Stream: wit-bindgen

Topic: Transitively Incompatible


view this post on Zulip Jeremy Slater (Mar 06 2024 at 03:49):

I'm trying to pass a Rust struct (exported) to a javascript class (imported) but I'm running into the message: "interface transitively depends on an interface in incompatible ways". Is it possible to do this?

view this post on Zulip Alex Crichton (Mar 06 2024 at 15:22):

You're hitting this error which more-or-less means that the imports/exports into the world aren't resolvable, and you probably need to export more things.

For example if interface A depends on B depends on C and you export only A and C the fix is to also export B

view this post on Zulip Alex Crichton (Mar 06 2024 at 15:23):

(sorry it's known the error message is particularly bad in this case)

view this post on Zulip Jeremy Slater (Mar 06 2024 at 15:50):

So in this example, is the issue that wit-bindgen wants interface b exported? I want interface b to be extended in JS and passed to Rust, but also pass a separate Rust export:

package test:pack;

interface a {
    use b.{factory};

    resource bbuilder {
        constructor();
        register-bfactory: func(bfactory: factory);
        build: func();
    }
}

interface b {
    use c.{c};

    resource b {
        b: func() -> string;
    }

    resource factory {
        b: func(c: c) -> b;
    }
}

interface c {
    resource c {
        c: func();
    }
}

world pack {
    import b;
    export a;
    export c;
}

view this post on Zulip Alex Crichton (Mar 06 2024 at 15:56):

yes you'll need to export b in addition to a/c

view this post on Zulip Alex Crichton (Mar 06 2024 at 15:56):

and that'll fix that error

view this post on Zulip Jeremy Slater (Mar 06 2024 at 16:05):

So the issue, is that when I export b, but need to send a JS extended b.Factory by extending an external JS implementation by calling jco with --map, then register-bfactory errors due to my extended b.Factory Not a valid "Factory" resource.

import { a, c } from "/out_dir/jcotest.js";
import { b } from "/packb.js";

class B extends b.B {
  b() {}
}

class Bfactory extends b.Factory {
  constructor() {
    super();
  }

  b(c) {
    console.log("Creating B");
    var out = c.c();
    console.log("C - ", out);
    return new B();
  }
}

var bFactory = new Bfactory();
var bBuilder = new a.Bbuilder();
bBuilder.registerBfactory(bFactory);
console.log("registered factories");
bBuilder.build();

view this post on Zulip Jeremy Slater (Mar 06 2024 at 16:07):

It seems like it's looking for an instance of b.Factory that was exported, and defined in the generated jcotest.js, not the imported b.Factory defined in packb.js

view this post on Zulip Alex Crichton (Mar 06 2024 at 16:14):

for that I might tag in @Guy Bedford who might know the JS/jco bits better than I

view this post on Zulip Jeremy Slater (Mar 06 2024 at 16:17):

And to add more context, this is related to the other question I raised that @Guy Bedford merged a resolution for. In my case, I would also be happy to export all three interfaces and merely extend an exported type (b.Factory), but I also seep the not a resource error there.

view this post on Zulip Guy Bedford (Mar 06 2024 at 19:01):

@Jeremy Slater this should work - the check that is being done by the function is just a arg instanceof Factory, which will be true for subclasses. So the issue is that you must export Factory to the component to import to begin with I think, perhaps it's undefined or something? The component must be able to import the base class itself (not an instance) to be able to do the instanceof check.

view this post on Zulip Jeremy Slater (Mar 07 2024 at 01:35):

I'm not seeing a simple instanceof in the codegen, it's looking up the resourceHandleSymbol:

 var rep3 = arg1[resourceHandleSymbol];
  if (rep3=== null) {
    throw new Error('Resource error: "Factory" lifetime expired.');
  }
  if (rep3=== undefined) {
    throw new Error('Resource error: Not a valid "Factory" resource.');
  }

This seems like an effect because i'm exporting and importing and it's expecting it to be a wasm compiled Rust struct?

view this post on Zulip Jeremy Slater (Mar 07 2024 at 01:42):

In fact if it just shuffle the wit around and remove c from being consumed by b, then I can get away without the export of b and yes in fact the codegen does a simple arg1 instanceof Factory.

view this post on Zulip Guy Bedford (Mar 07 2024 at 01:57):

@Jeremy Slater the imported Bfactory and exported Bfactory resources are completely different resource types, and are not interchangeable - if a function expects the exported resource type, you must pass the exported resource type not the imported resource type

view this post on Zulip Jeremy Slater (Mar 07 2024 at 15:04):

Sure, that's what I figured was the issue, so this leaves two questions; if I import and export b, can I specify that I want to use the imported b and not the exported b? If not, then can I not export b but still pass an exported resource to the imported b?

view this post on Zulip Guy Bedford (Mar 07 2024 at 16:22):

if I import and export b, can I specify that I want to use the imported b and not the exported b

The way to do this in WIT I believe is via use imported-interface.{foo as bar}; to disambiguate them by name even though they have the same name. Also https://github.com/WebAssembly/component-model/pull/308 may be related here.

This PR proposes to change how use works inside WIT worlds, based on some initial discussion in wit-bindgen/#822. Currently, use can be used with the same syntax in both interfaces and worlds. For...

view this post on Zulip Jeremy Slater (Mar 07 2024 at 20:38):

I'm not sure that even that PR inherently addresses what seems to be my issue. I think I'm having trouble explaining the issue. Basically wit has a problem if an exported func takes an argument of an imported resource that has a func taking an argument of an exported resource. wit can handle either direction of dependencies, exported with imported resource or exported with imported resource, but once it's a chain of export - import - export it breaks. It doesn't appear that import use is going to resolve this chaining problem. In my case I don't actually want to export b, it's entirely imported.

view this post on Zulip Joel Dice (Mar 07 2024 at 21:36):

Possibly related: https://github.com/WebAssembly/component-model/issues/272

The Component Model does not currently allow you to refer to exported types in imports, which feels asymmetric given that one can easily refer to imported types in exports. This becomes a practical...

view this post on Zulip Jeremy Slater (Mar 08 2024 at 15:04):

Thanks @Joel Dice for pointing that out, it certainly seems like that's related, although I can always have an export use an import and an import use an export, just not an export using an import using an export. I can loophole around this by getting the export on the js side and passing it to my js imported object like this:

import { interfacea, interfacec } from "/out_dir/jcotest.js";
import { interfaceb } from "/packb.js";

class Resourceb extends interfaceb.Resourceb {
  constructor(resourcec) {
    super();
    this.resourcec = resourcec;
  }

  funcb() {
    var result = this.resourcec.funcc();
    console.log("funcb - ", result);
    return result;
  }
}

class Factoryb extends interfaceb.Factoryb {
  create() {
    console.log("Creating B");
    var resourcec = builderb.resourcec();
    return new Resourceb(resourcec);
  }
}

var factoryb = new Factoryb();
var builderb = new interfacea.Buildera();
builderb.registerFactoryb(factoryb);
console.log("registered factories");
var bb = builderb.build();

This definitely has some code smell, but am I really hitting a limit of wit-bindgen and therefor this is the (only) way to do this?


Last updated: Dec 23 2024 at 12:05 UTC