Stream: wit-bindgen

Topic: Transitive resource dependencies


view this post on Zulip Landon James (Apr 20 2024 at 02:54):

I have a resource (call it some-resource) that needs to be used in several types defined in two different interfaces, and both of those interfaces are exported by the same world. I am defining this resource in a separate foo:utils package in its own interface. This all works fine when it comes to parsing the wit and generating the bindings. Those bindings include a SomeResource struct and a few impl blocks for that struct. But they do not include the GuestSomeResource trait that I would expect to allow me to actually implement the resource.

I tried adding the utils interface as an export to the world, but that caused a 1: interface transitively depends on an interface in incompatible ways error. Is there a way to get the Guest* trait for a transitive resource generated?

Using cargo-component-component 0.11.0 (wasi:040ec92) to build.

The files look more or less like the below, I'll try to get a full minimal repro up soon.

wit/world.wit

package component:transitive;

/// An example world for the component to target.
world example {
    export foo:a/a;
    export foo:b/b;
}

wit/packageA/a.wit

package foo:a;

interface a {
  use foo:utils/utils.{some-resource};

  variant a-variant {
    bar(some-resource)
  }
}

wit/packageB/b.wit

package foo:b;

interface b {
  use foo:utils/utils.{some-resource};

  variant b-variant {
    bar(some-resource)
  }
}

wit/packageUtils/utils.wit

package foo:utils;

interface utils {

  resource some-resource {
    constructor();
  }
}

view this post on Zulip Landon James (Apr 20 2024 at 05:18):

Heres a repro: https://github.com/landonxjames/wit-transitive-deps

In its current form it successfully builds the bindings (the rest of the code does't actually build), but with no GuestSomeResource trait. If I uncomment the utils export in wit/world.wit, which I would expect to generate the GuestSomeResource trait since that resource is now a direct export of the world

package component:transitive;

/// An example world for the component to target.
world example {
    export foo:a/a;
    export foo:b/b;

    //Uncommenting this export will cause a transitive dependency issue
    //export foo:utils/utils;
}

I get the error:

    1: interface transitively depends on an interface in incompatible ways
            --> /Volumes/workplace/transitive/wit/world.wit:5:18
             |
           5 |     export foo:a/a;
             |

But that doesn't quite make sense to me:

And adding foo:utils/utils as an export to the world doesn't seem like it would turn any of that into a circular dependency. But I also don't see any way of generating the GuestSomeResource trait without directly exporting the interface that defines some-resource.

wit transitive deps. Contribute to landonxjames/wit-transitive-deps development by creating an account on GitHub.

view this post on Zulip Alex Crichton (Apr 22 2024 at 14:47):

the tl;dr; to fix this is to export foo:a/a-child in addition to foo:utils/utils. I could go into more detail if you'd like as to why, but the general gist of it is this comment

CLI and Rust libraries for low-level manipulation of WebAssembly modules - bytecodealliance/wasm-tools

view this post on Zulip Landon James (Apr 22 2024 at 19:06):

Oh cool, adding the extra export worked. I am curious as to the why as it isn't really matching up with my intuition about dependency trees, but that was enough to unblock me so thank you!

view this post on Zulip Alex Crichton (Apr 23 2024 at 00:20):

For the longer form story, I'll simplify this and say you've got interfaces A, B, and C. C depends on B and B depends on A. The problem is that you're not allowed to export A & C without also exporting B. (here A is foo:utils/utils, B is foo:a/a-child, and C is foo:a/a).

Let's say that B is not exported. That means that C's imports from B must come from an imported instance of B. You want C's imports of A, however, to come from the exported instance of A. The import of B must also mean an import of A, so this means that you've got imports of A and B with exports of A and C.

In this situation C can, in theory, refer to two different A's. One is the imported copy through B and another is the explicitly exported copy. C, however, cannot at this time disambiguate which it would like to refer to. This also starts to set the stage for "expected Foo, found Foo" style errors where if C takes a type referring to B and additionally takes an A then the A that B references is different than the A in the argument list. This is all perfectly fine from a component model perspective in the sense that it can validate, however.

In the end we decided to make this an error for the time being to be conservative. It's something that could possibly be revisited in the future though. I may also be missing a constraint here since this is off the top of my head


Last updated: Oct 23 2024 at 20:03 UTC