Stream: jco

Topic: reject/resolve import name collision w instantiation helper


view this post on Zulip Mike M (Sep 17 2026 at 17:16):

Hi — looking for a sanity check before filing a GitHub issue.

Summary

With jco transpile --instantiation async, importing a WIT function named
reject (also resolve, and likely promise / gen / runNext /
maybeSyncReturn) produces JS that fails to load:

SyntaxError: Identifier 'reject' has already been declared

reject / resolve are legal WIT identifiers (not WIT keywords, not JS
keywords). The collision is with locals that the instantiation helper emits
in the same function scope.

WIT shape

package demo:jco;

interface respondent {
    reject: func(id: u32);
}

world demo {
    import respondent;
    export run: func(id: u32);
}

Observed

Generated instantiate() contains, in one scope:

const { reject } = imports['demo:jco/respondent'];
// ...
let promise, resolve, reject;   // instantiation helper

Expected

Either the import or the helper locals are deconflicted (e.g. reject$1), same as other name clashes — module loads cleanly.

Root cause (best guess)

Imports go through LocalNames in render_imports, but the helper is a raw string in transpile_bindgen.rs (let promise, resolve, reject; plus gen / runNext / maybeSyncReturn) and those names are never registered via exclude_globals / create_once. So the deconflicter never sees them.

Specific to --instantiation mode (default ESM puts the helper in a $init IIFE, so this is shadowing rather than redeclaration). Still present on bindgen from main last we checked, and on @bytecodealliance/jco@1.10.2.

Related precedent: #1898 / #1899 (exported eval not sanitized) — same “bindings must produce valid JS for any legal WIT name” principle.

Minimal repro

No wasm toolchain needed:

npm i @bytecodealliance/jco@1.10.2   # also fails on newer in our checks up to 1.34.0
# demo.wit as above
jco transpile demo.wit --stub --instantiation async --no-typescript -o out
node -e "import('./out/demo.js').then(() => console.log('OK'), e => console.log('FAIL:', e.message))"
# FAIL: Identifier 'reject' has already been declared

Control: rename reject → anything else → OK.

view this post on Zulip Victor Adossi (Sep 17 2026 at 17:28):

Hey @Mike M thanks for the report -- yeah that is definitely worth filing an issue for! I think basically the list that needs to be updated is here, but definitely this is worth filing an issue for and having the repro will be great to add to Jco.

view this post on Zulip Mike M (Sep 17 2026 at 18:01):

https://github.com/bytecodealliance/jco/issues/2134

view this post on Zulip Victor Adossi (Sep 18 2026 at 00:43):

Hey @Mike M Thanks for filing the issue! Do you want to take a stab at it? If not I'm happy to get to it (hopefully today/by monday!)

view this post on Zulip Mike M (Sep 18 2026 at 17:38):

yeah, sure! I wouldn't have presumed, but I'm happy to. Gimme a few.

view this post on Zulip Mike M (Sep 18 2026 at 17:58):

One thought I'd like your input on: since this isn't a forbidden JS keyword, it's bindgen’s own helper locals that weren’t reserved. So what do you think about either:

1) Extend global_names (or I could create a sibling call when instantiation/$init is emitted) with the helper locals:
promiseresolverejectgenrunNextmaybeSyncReturnnormalizeInstantiationError

Then an imported reject becomes reject$1 only when it actually collides — which is how LocalNames seem to work?

or...

2) perhaps even better (for a future where this chunk of helper code is modified again), leverage LocalNames directly and wrap those keyword in local_names.create_once(...), which seems to be the precedent for preventing collisions in the bindings, e.g.,

let promise = local_names.create_once("promise");
let resolve = local_names.create_once("resolve");
let reject  = local_names.create_once("reject");
let run_next = local_names.create_once("runNext");
// ...
uwrite!(output, "
  let {promise}, {resolve}, {reject};
  function {run_next} (value) {{ ... }}
  ...
", ...);

This has the benefit of not adding the name to global_names once, only to have another name pop up later that also needs to be added to global_names and leads to this same bug.

Thoughts?

view this post on Zulip Mike M (Sep 19 2026 at 02:29):

Here's the PR if you want to look at the impl I suggest above. Happy to tweak/pivot as needed. Pls advise when you have a moment.
https://github.com/bytecodealliance/jco/pull/2135


Last updated: Sep 20 2026 at 18:08 UTC