diff --git a/.cargo/config b/.cargo/config
deleted file mode 100644
index 8ebcc839710..00000000000
--- a/.cargo/config
+++ /dev/null
@@ -1,4 +0,0 @@
-# TODO: we shouldn't check this in to git, need to figure out how to avoid doing
-# that.
-[target.wasm32-unknown-unknown]
-runner = 'cargo run -p wasm-bindgen-cli --bin wasm-bindgen-test-runner --'
diff --git a/.cargo/config.toml b/.cargo/config.toml
new file mode 100644
index 00000000000..6440da0591e
--- /dev/null
+++ b/.cargo/config.toml
@@ -0,0 +1,2 @@
+[target.'cfg(target_arch = "wasm32")']
+runner = 'cargo run -p wasm-bindgen-cli --bin wasm-bindgen-test-runner --'
diff --git a/.github/FUNDING.yaml b/.github/FUNDING.yaml
new file mode 100644
index 00000000000..23be5540cd8
--- /dev/null
+++ b/.github/FUNDING.yaml
@@ -0,0 +1 @@
+github: [daxpedda]
diff --git a/.github/actions/setup-geckodriver/action.yml b/.github/actions/setup-geckodriver/action.yml
index 57d5578a512..89dd42cad35 100644
--- a/.github/actions/setup-geckodriver/action.yml
+++ b/.github/actions/setup-geckodriver/action.yml
@@ -2,5 +2,5 @@ name: 'Setup Geckodriver'
description: 'Setup Geckodriver'
runs:
- using: node12
+ using: node20
main: 'main.js'
diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml
new file mode 100644
index 00000000000..690cf6dfac4
--- /dev/null
+++ b/.github/dependabot.yaml
@@ -0,0 +1,20 @@
+version: 2
+
+updates:
+ - package-ecosystem: cargo
+ directory: /
+ schedule:
+ interval: daily
+ ignore:
+ - dependency-name: 'wasmparser'
+ - dependency-name: 'wasmprinter'
+ - dependency-name: 'wast'
+
+ - package-ecosystem: github-actions
+ directory: /
+ schedule:
+ interval: daily
+ groups:
+ github-actions:
+ patterns:
+ - "*"
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 91718ba9d25..d887cc699b7 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -5,6 +5,7 @@ on:
tags-ignore: [dev]
pull_request:
branches: [main]
+ workflow_dispatch:
defaults:
run:
shell: bash
@@ -15,58 +16,206 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
+permissions:
+ contents: read # to fetch code (actions/checkout)
+
jobs:
# Check Code style quickly by running `rustfmt` over all code
rustfmt:
name: Rustfmt
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
- - run: rustup update stable && rustup default stable
+ - uses: actions/checkout@v4
+ - run: rustup update --no-self-update stable && rustup default stable
- run: rustup component add rustfmt
- run: cargo fmt --all -- --check
+ # Check TOML style by using Taplo.
+ taplo:
+ name: Taplo
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+ - uses: taiki-e/install-action@v2
+ with:
+ tool: taplo-cli
+ - run: taplo fmt --check
+
# Run `cargo check` over everything
check:
name: Check
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
- - run: rustup update stable && rustup default stable
+ - uses: actions/checkout@v4
+ - run: rustup update --no-self-update stable && rustup default stable
- run: cargo check --all
+ - run: cargo check --no-default-features
+
+ # Run `cargo clippy` over everything
+ clippy:
+ name: Clippy
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - run: rustup update --no-self-update stable && rustup default stable
+ - run: rustup target add wasm32-unknown-unknown
+ - run: cargo clippy --no-deps --all-features -p wasm-bindgen-backend -- -D warnings
+ - run: cargo clippy --no-deps --all-features -p wasm-bindgen -- -D warnings
+ - run: cargo clippy --no-deps --all-features -p wasm-bindgen-cli -- -D warnings
+ - run: cargo clippy --no-deps --all-features -p wasm-bindgen-cli-support -- -D warnings
+ - run: cargo clippy --no-deps --all-features -p example-tests -- -D warnings
+ - run: cargo clippy --no-deps --all-features -p wasm-bindgen-externref-xform -- -D warnings
+ - run: cargo clippy --no-deps --all-features --target wasm32-unknown-unknown -p wasm-bindgen-futures -- -D warnings
+ - run: cargo clippy --no-deps --all-features -p wasm-bindgen-macro -- -D warnings
+ - run: cargo clippy --no-deps --all-features -p wasm-bindgen-macro-support -- -D warnings
+ - run: cargo clippy --no-deps --all-features -p wasm-bindgen-multi-value-xform -- -D warnings
+ - run: cargo clippy --no-deps --all-features -p wasm-bindgen-shared -- -D warnings
+ - run: cargo clippy --no-deps --all-features --target wasm32-unknown-unknown -p wasm-bindgen-test -- -D warnings
+ - run: cargo clippy --no-deps --all-features -p wasm-bindgen-test-macro -- -D warnings
+ - run: cargo clippy --no-deps --all-features -p wasm-bindgen-threads-xform -- -D warnings
+ - run: cargo clippy --no-deps --all-features --target wasm32-unknown-unknown -p typescript-tests -- -D warnings
+ - run: cargo clippy --no-deps --all-features -p wasm-bindgen-wasm-conventions -- -D warnings
+ - run: cargo clippy --no-deps --all-features -p wasm-bindgen-wasm-interpreter -- -D warnings
+ - run: cargo clippy --no-deps --all-features -p wasm-bindgen-webidl -- -D warnings
+ - run: cargo clippy --no-deps --all-features -p webidl-tests -- -D warnings
+ - run: cargo clippy --no-deps --all-features --target wasm32-unknown-unknown -p wasm-bindgen-benchmark -- -D warnings
+
+ # Run `cargo clippy` over web-sys and js-sys crates
+ clippy_web_sys:
+ name: Clippy (web-sys)
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - run: rustup update --no-self-update stable && rustup default stable
+ - run: rustup target add wasm32-unknown-unknown
+ - run: cargo clippy --no-deps --all-features --target wasm32-unknown-unknown -p js-sys --all-targets -- -D warnings
+ - run: cargo clippy --no-deps --all-features --target wasm32-unknown-unknown -p web-sys --all-targets -- -D warnings
+
+ # Run `cargo clippy` over crates that support `no_std`
+ clippy_no_std:
+ name: Clippy `no_std`
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - run: rustup update --no-self-update stable && rustup default stable
+ - run: rustup target add wasm32-unknown-unknown
+ - run: cargo clippy --no-deps --no-default-features --target wasm32-unknown-unknown -p wasm-bindgen -- -D warnings
+ - run: cargo clippy --no-deps --no-default-features --target wasm32-unknown-unknown -p js-sys -- -D warnings
+ - run: cargo clippy --no-deps --no-default-features --target wasm32-unknown-unknown -p web-sys -- -D warnings
+ - run: cargo clippy --no-deps --no-default-features --target wasm32-unknown-unknown -p wasm-bindgen-futures -- -D warnings
+ - run: cargo clippy --no-deps --no-default-features --target wasm32-unknown-unknown -p wasm-bindgen-test -- -D warnings
+
+ # Run `cargo clippy` over crates that support `no_std` with `target_feature = "atomics"` support.
+ clippy_no_std_atomics:
+ name: Clippy `no_std` with `atomics`
+ runs-on: ubuntu-latest
+ env:
+ RUSTFLAGS: -Ctarget-feature=+atomics,+bulk-memory
+ steps:
+ - uses: actions/checkout@v4
+ - run: rustup default nightly-2024-07-06
+ - run: rustup target add wasm32-unknown-unknown
+ - run: rustup component add clippy rust-src
+ - run: cargo clippy --no-deps --no-default-features --target wasm32-unknown-unknown -p wasm-bindgen -Zbuild-std=core,alloc -- -D warnings
+ - run: cargo clippy --no-deps --no-default-features --target wasm32-unknown-unknown -p js-sys -Zbuild-std=core,alloc -- -D warnings
+ - run: cargo clippy --no-deps --no-default-features --target wasm32-unknown-unknown -p web-sys -Zbuild-std=core,alloc -- -D warnings
+ - run: cargo clippy --no-deps --no-default-features --target wasm32-unknown-unknown -p wasm-bindgen-futures -Zbuild-std=core,alloc -- -D warnings
+ - run: cargo clippy --no-deps --no-default-features --target wasm32-unknown-unknown -p wasm-bindgen-test -Zbuild-std=core,alloc -- -D warnings
+
+ # Run `cargo clippy` over the project
+ clippy_project:
+ name: Clippy (project)
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - run: rustup update --no-self-update stable && rustup default stable
+ - run: rustup target add wasm32-unknown-unknown
+ - run: cargo clippy --no-deps --all-features --target wasm32-unknown-unknown -- -D warnings
+ - run: cargo clippy --no-deps --all-features --target wasm32-unknown-unknown --tests -- -D warnings
+ - run: for i in examples/*/; do cd "$i"; cargo +stable clippy --no-deps --all-features --target wasm32-unknown-unknown -- -D warnings || exit 1; cd ../..; done
test_wasm_bindgen:
- name: "Run wasm-bindgen crate tests (unix)"
+ strategy:
+ matrix:
+ runs:
+ - name: "wasm-bindgen"
+ run: |
+ cargo test --target wasm32-unknown-unknown
+ cargo test --target wasm32-unknown-unknown -p wasm-bindgen-futures
+ - name: "wasm-bindgen (serde-serialize)"
+ run: cargo test --target wasm32-unknown-unknown --features serde-serialize
+ - name: "wasm-bindgen (enable-interning)"
+ run: cargo test --target wasm32-unknown-unknown --features enable-interning
+ name: "Run wasm-bindgen crate tests (${{ matrix.runs.name }})"
runs-on: ubuntu-latest
+ env:
+ WASM_BINDGEN_SPLIT_LINKED_MODULES: 1
steps:
- - uses: actions/checkout@v2
- - run: rustup update stable && rustup default stable
+ - uses: actions/checkout@v4
+ - run: rustup update --no-self-update stable && rustup default stable
- run: rustup target add wasm32-unknown-unknown
- - uses: actions/setup-node@v2
+ - uses: actions/setup-node@v4
with:
- node-version: '16'
+ node-version: '20'
- uses: ./.github/actions/setup-geckodriver
- - run: cargo test --target wasm32-unknown-unknown
- - run: cargo test --target wasm32-unknown-unknown
- - run: cargo test --target wasm32-unknown-unknown --features serde-serialize
- - run: cargo test --target wasm32-unknown-unknown --features enable-interning
- - run: cargo test --target wasm32-unknown-unknown -p no-std
- - run: cargo test --target wasm32-unknown-unknown -p wasm-bindgen-futures
- - run: cargo test --target wasm32-unknown-unknown --test wasm
- env:
- WASM_BINDGEN_WEAKREF: 1
+ - run: ${{ matrix.runs.run }}
+
+ test_wasm_bindgen_wasm:
+ name: "Run wasm-bindgen wasm test"
+ runs-on: ubuntu-latest
+ env:
+ WASM_BINDGEN_SPLIT_LINKED_MODULES: 1
+ steps:
+ - uses: actions/checkout@v4
+ - run: rustup update --no-self-update stable && rustup default stable
+ - run: rustup target add wasm32-unknown-unknown
+ - uses: actions/setup-node@v4
+ with:
+ node-version: '20'
- run: cargo test --target wasm32-unknown-unknown --test wasm
env:
- WASM_BINDGEN_WEAKREF: 1
WASM_BINDGEN_NO_DEBUG: 1
- - run: cargo test --target wasm32-unknown-unknown --test wasm --features serde-serialize
- env:
- WASM_BINDGEN_WEAKREF: 1
+
+ test_wasm_bindgen_envs:
+ strategy:
+ matrix:
+ envs:
+ - name: "externref"
+ env:
+ WASM_BINDGEN_EXTERNREF: 1
+ - name: "multi-value"
+ env:
+ WASM_BINDGEN_MULTI_VALUE: 1
+ name: "Run wasm-bindgen crate tests with ${{ matrix.envs.name }} feature"
+ runs-on: ubuntu-latest
+ env:
+ WASM_BINDGEN_SPLIT_LINKED_MODULES: 1
+ WASM_BINDGEN_EXTERNREF: ${{ matrix.envs.WASM_BINDGEN_EXTERNREF }}
+ WASM_BINDGEN_MULTI_VALUE: ${{ matrix.envs.WASM_BINDGEN_MULTI_VALUE }}
+ steps:
+ - uses: actions/checkout@v4
+ - run: rustup update --no-self-update stable && rustup default stable
+ - run: rustup target add wasm32-unknown-unknown
+ - uses: actions/setup-node@v4
+ with:
+ node-version: '20'
- run: cargo test --target wasm32-unknown-unknown
- env:
- WASM_BINDGEN_EXTERNREF: 1
- NODE_ARGS: --experimental-wasm-reftypes
+ test_threads:
+ name: "Run wasm-bindgen crate tests with multithreading"
+ runs-on: ubuntu-latest
+ env:
+ WASM_BINDGEN_SPLIT_LINKED_MODULES: 1
+ steps:
+ - uses: actions/checkout@v4
+ - run: rustup default nightly-2024-07-06
+ - run: rustup target add wasm32-unknown-unknown
+ - run: rustup component add rust-src
+ - run: |
+ RUSTFLAGS='-C target-feature=+atomics,+bulk-memory,+mutable-globals' \
+ cargo test --target wasm32-unknown-unknown -Z build-std=std,panic_abort
# I don't know why this is failing so comment this out for now, but ideally
# this would be figured out at some point and solved.
@@ -74,12 +223,12 @@ jobs:
# name: "Run wasm-bindgen crate tests (Windows)"
# runs-on: windows-latest
# steps:
- # - uses: actions/checkout@v2
- # - run: rustup update stable && rustup default stable
+ # - uses: actions/checkout@v4
+ # - run: rustup update --no-self-update stable && rustup default stable
# - run: rustup target add wasm32-unknown-unknown
- # - uses: actions/setup-node@v2
+ # - uses: actions/setup-node@v4
# with:
- # node-version: '16'
+ # node-version: '20'
# - uses: ./.github/actions/setup-geckodriver
# - run: cargo test --target wasm32-unknown-unknown
# env:
@@ -91,120 +240,99 @@ jobs:
# WBINDGEN_I_PROMISE_JS_SYNTAX_WORKS_IN_NODE: 1
# - run: cargo build --manifest-path crates/web-sys/Cargo.toml --target wasm32-unknown-unknown --features "Node Window Document"
- test_wasm_bindgen_nightly:
- name: "Run wasm-bindgen crate tests (nightly)"
+ # This checks that the output of the CLI is actually valid JavaScript and TypeScript
+ test_cli_reference_typescript:
+ name: Run CLI reference TypeScript check
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
- - run: rustup default nightly-2021-09-02
- - run: rustup target add wasm32-unknown-unknown
- - uses: actions/setup-node@v2
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
with:
- node-version: '16'
- - run: cargo test --target wasm32-unknown-unknown --features nightly --test wasm
+ node-version: 'lts/*'
+ - run: npm i -g typescript
+ - run: npm i --save @types/node @types/deno
+ - name: Check TypeScript output
+ run: tsc --noEmit --skipLibCheck --lib esnext,dom $(echo crates/cli/tests/reference/*.d.ts)
+ - name: Check JavaScript output
+ run: tsc --noEmit --skipLibCheck --lib esnext,dom --module esnext --allowJs $(echo crates/cli/tests/reference/*.js)
test_native:
name: Run native tests
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
- - run: rustup update stable && rustup default stable
+ - uses: actions/checkout@v4
+ - run: rustup update --no-self-update stable && rustup default stable
- run: rustup target add wasm32-unknown-unknown
- - uses: actions/setup-node@v2
+ - uses: actions/setup-node@v4
with:
- node-version: '16'
+ node-version: '20'
- run: cargo test
- run: cargo test -p wasm-bindgen-cli-support
- run: cargo test -p wasm-bindgen-cli
- run: cargo test -p wasm-bindgen-externref-xform
+ - run: cargo test -p wasm-bindgen-macro-support
- run: cargo test -p wasm-bindgen-multi-value-xform
- run: cargo test -p wasm-bindgen-wasm-interpreter
- run: cargo test -p wasm-bindgen-futures
- run: cargo test -p wasm-bindgen-shared
- test_web_sys:
- name: "Run web-sys crate tests"
+ test_with_geckodriver:
+ strategy:
+ matrix:
+ runs:
+ - name: "web-sys"
+ run: |
+ cargo build --manifest-path crates/web-sys/Cargo.toml --target wasm32-unknown-unknown
+ cargo build --manifest-path crates/web-sys/Cargo.toml --target wasm32-unknown-unknown --features Node
+ cargo build --manifest-path crates/web-sys/Cargo.toml --target wasm32-unknown-unknown --features Element
+ cargo build --manifest-path crates/web-sys/Cargo.toml --target wasm32-unknown-unknown --features Window
+ - name: "web-sys (all features)"
+ run: cargo test --manifest-path crates/web-sys/Cargo.toml --target wasm32-unknown-unknown --all-features
+ - name: "web-sys (unstable, all features)"
+ run: cargo test --manifest-path crates/web-sys/Cargo.toml --target wasm32-unknown-unknown --all-features
+ env:
+ RUSTFLAGS: --cfg=web_sys_unstable_apis
+ - name: "js-sys"
+ run: cargo test -p js-sys --target wasm32-unknown-unknown
+ - name: "js-sys (unstable)"
+ run: cargo test -p js-sys --target wasm32-unknown-unknown
+ env:
+ RUSTFLAGS: --cfg=js_sys_unstable_apis
+ - name: "wasm-bindgen-webidl"
+ run: cargo test -p wasm-bindgen-webidl
+ - name: "webidl-tests"
+ run: cargo test -p webidl-tests --target wasm32-unknown-unknown
+ env:
+ WBINDGEN_I_PROMISE_JS_SYNTAX_WORKS_IN_NODE: 1
+ - name: "webidl-tests (unstable)"
+ run: cargo test -p webidl-tests --target wasm32-unknown-unknown
+ env:
+ RUSTFLAGS: --cfg=web_sys_unstable_apis
+ - name: "typescript-tests"
+ run: cd crates/typescript-tests && ./run.sh
+ name: "Run ${{ matrix.runs.name }} tests (with geckodriver)"
+ env:
+ RUSTFLAGS: ${{ matrix.runs.env.RUSTFLAGS }}
+ WBINDGEN_I_PROMISE_JS_SYNTAX_WORKS_IN_NODE: ${{ matrix.runs.env.WBINDGEN_I_PROMISE_JS_SYNTAX_WORKS_IN_NODE }}
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
- - run: rustup update stable && rustup default stable
+ - uses: actions/checkout@v4
+ - run: rustup update --no-self-update stable && rustup default stable
- run: rustup target add wasm32-unknown-unknown
- - uses: actions/setup-node@v2
+ - uses: actions/setup-node@v4
with:
- node-version: '16'
+ node-version: '20'
- uses: ./.github/actions/setup-geckodriver
- - run: cargo build --manifest-path crates/web-sys/Cargo.toml --target wasm32-unknown-unknown
- - run: cargo build --manifest-path crates/web-sys/Cargo.toml --target wasm32-unknown-unknown --features Node
- - run: cargo build --manifest-path crates/web-sys/Cargo.toml --target wasm32-unknown-unknown --features Element
- - run: cargo build --manifest-path crates/web-sys/Cargo.toml --target wasm32-unknown-unknown --features Window
- - run: cargo test --manifest-path crates/web-sys/Cargo.toml --target wasm32-unknown-unknown --all-features
- - run: cargo test --manifest-path crates/web-sys/Cargo.toml --target wasm32-unknown-unknown --all-features
- env:
- RUSTFLAGS: --cfg=web_sys_unstable_apis
-
- check_web_sys:
- name: "Verify that web-sys is compiled correctly"
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2
- - run: rustup update stable && rustup default stable
- - run: cd crates/web-sys && cargo run --release --package wasm-bindgen-webidl -- webidls src/features
- - run: git diff --exit-code
-
- test_js_sys:
- name: "Run js-sys crate tests"
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2
- - run: rustup update stable && rustup default stable
- - run: rustup target add wasm32-unknown-unknown
- - uses: actions/setup-node@v2
- with:
- node-version: '16'
- - uses: ./.github/actions/setup-geckodriver
- - run: cargo test -p js-sys --target wasm32-unknown-unknown
- - run: cargo test -p js-sys --target wasm32-unknown-unknown
- env:
- RUSTFLAGS: --cfg=js_sys_unstable_apis
-
- test_webidl:
- name: "Run wasm-bindgen-webidl crate tests"
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2
- - run: rustup update stable && rustup default stable
- - run: rustup target add wasm32-unknown-unknown
- - uses: actions/setup-node@v2
- with:
- node-version: '16'
- - run: cargo test -p wasm-bindgen-webidl
- - run: cargo test -p webidl-tests --target wasm32-unknown-unknown
- env:
- WBINDGEN_I_PROMISE_JS_SYNTAX_WORKS_IN_NODE: 1
- - run: cargo test -p webidl-tests --target wasm32-unknown-unknown
- env:
- RUSTFLAGS: --cfg=web_sys_unstable_apis
-
- test_typescript_output:
- name: "Test TypeScript output of wasm-bindgen"
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2
- - run: rustup update stable && rustup default stable
- - run: rustup target add wasm32-unknown-unknown
- - uses: actions/setup-node@v2
- with:
- node-version: '16'
- - run: cd crates/typescript-tests && ./run.sh
+ - run: ${{ matrix.runs.run }}
test_deno:
name: "Build and test the deno example"
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
- - run: rustup update stable && rustup default stable
+ - uses: actions/checkout@v4
+ - run: rustup update --no-self-update stable && rustup default stable
- run: rustup target add wasm32-unknown-unknown
- - uses: denoland/setup-deno@v1
+ - uses: denoland/setup-deno@v2
with:
deno-version: v1.x
- run: cd examples/deno && ./build.sh && deno run --allow-read test.ts
@@ -213,67 +341,119 @@ jobs:
name: Run UI compile-fail tests
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
- - run: rustup update 1.56.0 && rustup default 1.56.0
+ - uses: actions/checkout@v4
+ - run: rustup update --no-self-update 1.78.0 && rustup default 1.78.0
- run: cargo test -p wasm-bindgen-macro
+ - run: cargo test -p wasm-bindgen-test-macro
+
+ build_webidl:
+ name: "Verify that web-sys is compiled correctly"
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - run: rustup update --no-self-update stable && rustup default stable
+ - run: cd crates/web-sys && cargo run --release --package wasm-bindgen-webidl -- webidls src/features ./Cargo.toml
+ - run: git diff --exit-code
build_examples:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
- - run: rustup update stable && rustup default stable
+ - uses: actions/checkout@v4
+ - run: rustup update --no-self-update stable && rustup default stable
- run: rustup target add wasm32-unknown-unknown
- run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -f
- run: |
cargo build -p wasm-bindgen-cli
ln -snf `pwd`/target/debug/wasm-bindgen $(dirname `which cargo`)/wasm-bindgen
- - run: mv _package.json package.json && npm install && rm package.json
+ # crete a workspace of all examples and install their dependencies at once
+ # sed is used to convert the literal "\n" to a newline character
- run: |
- for dir in `ls examples | grep -v README | grep -v asm.js | grep -v raytrace | grep -v without-a-bundler | grep -v wasm-in-web-worker | grep -v websockets | grep -v webxr | grep -v deno`; do
- (cd examples/$dir &&
- ln -fs ../../node_modules . &&
- npm run build -- --output-path ../../exbuild/$dir) || exit 1;
- done
- - uses: actions/upload-artifact@v2
+ [ -f pnpm-workspace.yaml ] || (echo 'packages:\n - "examples/*"' > pnpm-workspace.yaml && sed -i -e 's/\\n/\n/g' pnpm-workspace.yaml)
+ - run: |
+ [ -f package.json ] || (echo '{}' > package.json)
+ - run: corepack pnpm install -r
+ - run: |
+ function build() {
+ (cd examples/$1 &&
+ (corepack pnpm run build -- --output-path ../../exbuild/$1 ||
+ (./build.sh && mkdir -p ../../exbuild/$1 && cp -r ./* ../../exbuild/$1 && rm ../../exbuild/$1/**/.gitignore))
+ ) || exit 1;
+ }
+ export -f build
+ ls examples | grep -v README | grep -v raytrace | grep -v deno | grep -v wasm-audio-worklet | parallel -j4 build
+ env:
+ RUSTFLAGS: --cfg=web_sys_unstable_apis
+ - uses: actions/upload-artifact@v4
with:
name: examples1
path: exbuild
- build_raytrace:
+ build_nightly:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
- - run: rustup default nightly-2021-09-02
+ - uses: actions/checkout@v4
+ - run: rustup default nightly-2024-07-06
- run: rustup target add wasm32-unknown-unknown
- run: rustup component add rust-src
- run: |
- sed -i 's/python/#python/' examples/raytrace-parallel/build.sh
- (cd examples/raytrace-parallel && ./build.sh)
- mkdir exbuild
- cp examples/raytrace-parallel/*.{js,html,wasm} exbuild
- - uses: actions/upload-artifact@v2
+ for dir in raytrace-parallel wasm-audio-worklet; do
+ (cd examples/$dir &&
+ ./build.sh && mkdir -p ../../exbuild/$dir && cp -r ./* ../../exbuild/$dir
+ ) || exit 1;
+ done
+ - uses: actions/upload-artifact@v4
with:
name: examples2
path: exbuild
+ test_examples:
+ needs:
+ - build_examples
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/download-artifact@v4
+ with:
+ name: examples1
+ path: exbuild
+ - run: rustup update --no-self-update stable && rustup default stable
+ - run: cargo test -p example-tests
+ env:
+ EXBUILD: ${{ github.workspace }}/exbuild
+
+ test_nightly:
+ needs:
+ - build_nightly
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/download-artifact@v4
+ with:
+ name: examples2
+ path: exbuild
+ - run: rustup update --no-self-update stable && rustup default stable
+ - run: cargo test -p example-tests --no-default-features --features nightly
+ env:
+ EXBUILD: ${{ github.workspace }}/exbuild
+
build_benchmarks:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
- - run: rustup update stable && rustup default stable
+ - uses: actions/checkout@v4
+ - run: rustup update --no-self-update stable && rustup default stable
- run: rustup target add wasm32-unknown-unknown
- run: cargo build --manifest-path benchmarks/Cargo.toml --release --target wasm32-unknown-unknown
- run: cargo run -p wasm-bindgen-cli -- target/wasm32-unknown-unknown/release/wasm_bindgen_benchmark.wasm --out-dir benchmarks/pkg --target web
- - uses: actions/upload-artifact@v2
+ - uses: actions/upload-artifact@v4
with:
name: benchmarks
path: benchmarks
- dist_linux:
+ dist_linux_x86_64_musl:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
- - run: rustup update stable && rustup default stable
+ - uses: actions/checkout@v4
+ - run: rustup update --no-self-update stable && rustup default stable
- run: rustup target add x86_64-unknown-linux-musl
- run: sudo apt update -y && sudo apt install musl-tools -y
- run: |
@@ -281,33 +461,64 @@ jobs:
strip -g target/x86_64-unknown-linux-musl/release/wasm-bindgen
strip -g target/x86_64-unknown-linux-musl/release/wasm-bindgen-test-runner
strip -g target/x86_64-unknown-linux-musl/release/wasm2es6js
- - uses: actions/upload-artifact@v2
+ - uses: actions/upload-artifact@v4
with:
- name: dist_linux
+ name: dist_linux_x86_64_musl
path: "target/x86_64-unknown-linux-musl/release/wasm*"
- dist_macos:
+ dist_linux_aarch64_gnu:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - run: rustup update --no-self-update stable && rustup default stable
+ - run: rustup target add aarch64-unknown-linux-gnu
+ - run: sudo apt update -y && sudo apt install gcc-aarch64-linux-gnu -y
+ - run: |
+ cargo build --manifest-path crates/cli/Cargo.toml --target aarch64-unknown-linux-gnu --features vendored-openssl --release
+ env:
+ CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
+ - uses: actions/upload-artifact@v4
+ with:
+ name: dist_linux_aarch64_gnu
+ path: "target/aarch64-unknown-linux-gnu/release/wasm*"
+
+ dist_macos_x86_64:
runs-on: macos-latest
steps:
- - uses: actions/checkout@v2
- - run: rustup update stable && rustup default stable
- - run: cargo build --manifest-path crates/cli/Cargo.toml --release
+ - uses: actions/checkout@v4
+ - run: rustup update --no-self-update stable && rustup default stable
+ - run: rustup target add x86_64-apple-darwin
+ - run: cargo build --manifest-path crates/cli/Cargo.toml --target x86_64-apple-darwin --release
env:
MACOSX_DEPLOYMENT_TARGET: 10.7
- - uses: actions/upload-artifact@v2
+ - uses: actions/upload-artifact@v4
with:
- name: dist_macos
+ name: dist_macos_x86_64
+ path: "target/x86_64-apple-darwin/release/wasm*"
+
+ dist_macos_aarch64:
+ runs-on: macos-latest
+ steps:
+ - uses: actions/checkout@v4
+ - run: rustup update --no-self-update stable && rustup default stable
+ - run: |
+ cargo build --manifest-path crates/cli/Cargo.toml --release
+ env:
+ MACOSX_DEPLOYMENT_TARGET: 10.7
+ - uses: actions/upload-artifact@v4
+ with:
+ name: dist_macos_aarch64
path: "target/release/wasm*"
dist_windows:
runs-on: windows-latest
steps:
- - uses: actions/checkout@v2
- - run: rustup update stable && rustup default stable
+ - uses: actions/checkout@v4
+ - run: rustup update --no-self-update stable && rustup default stable
- run: cargo build --manifest-path crates/cli/Cargo.toml --release
env:
RUSTFLAGS: -Ctarget-feature=+crt-static
- - uses: actions/upload-artifact@v2
+ - uses: actions/upload-artifact@v4
with:
name: dist_windows
path: "target/release/wasm*"
@@ -315,12 +526,12 @@ jobs:
doc_book:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- run: |
- curl -L https://github.com/rust-lang-nursery/mdBook/releases/download/v0.3.0/mdbook-v0.3.0-x86_64-unknown-linux-gnu.tar.gz | tar xzf -
+ curl -L https://github.com/rust-lang/mdBook/releases/download/v0.4.28/mdbook-v0.4.28-x86_64-unknown-linux-gnu.tar.gz | tar xzf -
echo $PWD >> $GITHUB_PATH
- run: (cd guide && mdbook build)
- - uses: actions/upload-artifact@v2
+ - uses: actions/upload-artifact@v4
with:
name: doc_book
path: guide/book/html
@@ -328,38 +539,107 @@ jobs:
doc_api:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
- - run: rustup update nightly && rustup default nightly
- - run: cargo doc --no-deps --features 'nightly serde-serialize'
+ - uses: actions/checkout@v4
+ - run: rustup update --no-self-update nightly && rustup default nightly
+ - run: cargo doc --no-deps --features 'serde-serialize'
- run: cargo doc --no-deps --manifest-path crates/js-sys/Cargo.toml
- run: cargo doc --no-deps --manifest-path crates/web-sys/Cargo.toml --all-features
env:
RUSTDOCFLAGS: --cfg=web_sys_unstable_apis
- - run: cargo doc --no-deps --manifest-path crates/futures/Cargo.toml
+ - run: cargo doc --no-deps --manifest-path crates/futures/Cargo.toml --all-features
+ env:
+ RUSTDOCFLAGS: --cfg=docsrs
- run: tar czvf docs.tar.gz target/doc
- - uses: actions/upload-artifact@v2
+ - uses: actions/upload-artifact@v4
with:
name: doc_api
path: docs.tar.gz
+ msrv-resolver:
+ name: Check feature resolver version 1 compatibility
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ rust:
+ - 1.57
+ - stable
+ target:
+ - x86_64-unknown-linux-gnu
+ - wasm32-unknown-unknown
+ features:
+ - --no-default-features
+ - --no-default-features --features std
+ - --no-default-features --features msrv
+ - ""
+ defaults:
+ run:
+ working-directory: crates/msrv/resolver
+ steps:
+ - uses: actions/checkout@v4
+ - run: rustup update --no-self-update ${{ matrix.rust }} && rustup default ${{ matrix.rust }} && rustup target add ${{ matrix.target }}
+ - if: matrix.rust == '1.57'
+ run: |
+ cargo update -p bumpalo --precise 3.12.0
+ cargo update -p log --precise 0.4.18
+ - run: cargo build --target ${{ matrix.target }} ${{ matrix.features }}
+
+ msrv-lib:
+ name: Check MSRV for libraries
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ target:
+ - x86_64-unknown-linux-gnu
+ - wasm32-unknown-unknown
+ features:
+ - --no-default-features
+ - --no-default-features --features std
+ - --no-default-features --features msrv
+ - ""
+ defaults:
+ run:
+ working-directory: crates/msrv/lib
+ steps:
+ - uses: actions/checkout@v4
+ - run: rustup update --no-self-update 1.57 && rustup default 1.57 && rustup target add ${{ matrix.target }}
+ - run: cargo build --target ${{ matrix.target }} ${{ matrix.features }}
+
+ msrv-cli:
+ name: Check MSRV for CLI tools
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: crates/msrv/cli
+ steps:
+ - uses: actions/checkout@v4
+ - run: rustup update --no-self-update 1.76 && rustup default 1.76
+ - run: cargo build
+
deploy:
+ permissions:
+ contents: write # to push changes in repo (jamesives/github-pages-deploy-action)
+
+ if: github.repository == 'rustwasm/wasm-bindgen'
needs:
- doc_api
- doc_book
- - dist_linux
- - dist_macos
+ - dist_linux_x86_64_musl
+ - dist_linux_aarch64_gnu
+ - dist_macos_x86_64
+ - dist_macos_aarch64
- dist_windows
- build_examples
- - build_raytrace
+ - build_nightly
- build_benchmarks
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
with:
fetch-depth: 0
- - run: rustup update nightly && rustup default nightly
- - uses: actions/download-artifact@v2
+ - uses: actions/download-artifact@v4
with:
path: artifacts
- run: find artifacts
@@ -381,11 +661,14 @@ jobs:
tmp/$name/
chmod +x tmp/$name/wasm*
tar czvf gh-release/$name.tar.gz -C tmp $name
+ sha256sum gh-release/$name.tar.gz > gh-release/$name.tar.gz.sha256sum
}
- mk x86_64-unknown-linux-musl dist_linux
- mk x86_64-apple-darwin dist_macos
+ mk x86_64-unknown-linux-musl dist_linux_x86_64_musl
+ mk aarch64-unknown-linux-gnu dist_linux_aarch64_gnu
+ mk x86_64-apple-darwin dist_macos_x86_64
+ mk aarch64-apple-darwin dist_macos_aarch64
mk x86_64-pc-windows-msvc dist_windows
- - uses: actions/upload-artifact@v2
+ - uses: actions/upload-artifact@v4
with:
name: gh-release
path: gh-release
@@ -394,20 +677,28 @@ jobs:
tar xf artifacts/doc_api/docs.tar.gz
mv target/doc gh-pages/api
mv artifacts/examples1 gh-pages/exbuild
- mv artifacts/examples2 gh-pages/exbuild/raytrace-parallel
+ mv artifacts/examples2/* gh-pages/exbuild
mv artifacts/benchmarks gh-pages/benchmarks
tar czf gh-pages.tar.gz gh-pages
- - uses: actions/upload-artifact@v2
+ - uses: actions/upload-artifact@v4
with:
name: gh-pages
path: gh-pages.tar.gz
- - uses: JamesIves/github-pages-deploy-action@4.1.4
+ - uses: JamesIves/github-pages-deploy-action@v4
with:
branch: gh-pages
folder: gh-pages
single-commit: true
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
- - uses: softprops/action-gh-release@v1
+ - uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/')
with:
- files: "gh-release/*.tar.gz"
+ files: "gh-release/*.tar.gz*"
+ - uses: dtolnay/rust-toolchain@stable
+ if: startsWith(github.ref, 'refs/tags/')
+ - run: rustc publish.rs
+ if: startsWith(github.ref, 'refs/tags/')
+ - run: ./publish publish
+ if: startsWith(github.ref, 'refs/tags/')
+ env:
+ CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_PUBLISH_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 8cdf73b9e4f..cb63ac51317 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,8 +5,11 @@ Cargo.lock
node_modules
package-lock.json
npm-shrinkwrap.json
+pnpm-lock.yaml
yarn.lock
/publish
/publish.exe
.vscode
webdriver.json
+benchmarks/pkg
+/crates/msrv/*/target
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0316d3f176b..08dcd104a0d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,983 @@
# `wasm-bindgen` Change Log
--------------------------------------------------------------------------------
+## [0.2.100](https://github.com/rustwasm/wasm-bindgen/compare/0.2.99...0.2.100)
+
+Released 2025-01-12
+
+### Added
+
+* Add attributes to overwrite return (``unchecked_return_type`) and parameter types (`unchecked_param_type`), descriptions (`return_description` and `param_description`) as well as parameter names (`js_name`) for exported functions and methods. See the guide for more details.
+ [#4394](https://github.com/rustwasm/wasm-bindgen/pull/4394)
+
+* Add a `copy_to_uninit()` method to all `TypedArray`s. It takes `&mut [MaybeUninit]` and returns `&mut [T]`.
+ [#4340](https://github.com/rustwasm/wasm-bindgen/pull/4340)
+
+* Add test coverage support for Node.js.
+ [#4348](https://github.com/rustwasm/wasm-bindgen/pull/4348)
+
+* Support importing memory and using `wasm_bindgen::module()` in Node.js.
+ [#4349](https://github.com/rustwasm/wasm-bindgen/pull/4349)
+
+* Add `--list`, `--ignored`, `--exact` and `--nocapture` to `wasm-bindgen-test-runner`, analogous to `cargo test`.
+ [#4356](https://github.com/rustwasm/wasm-bindgen/pull/4356)
+
+* Add bindings to `Date.to_locale_time_string_with_options`.
+ [#4384](https://github.com/rustwasm/wasm-bindgen/pull/4384)
+
+* `#[wasm_bindgen]` now correctly applies `#[cfg(...)]`s in `struct`s.
+ [#4351](https://github.com/rustwasm/wasm-bindgen/pull/4351)
+
+
+### Changed
+
+* Optional parameters are now typed as `T | undefined | null` to reflect the actual JS behavior.
+ [#4188](https://github.com/rustwasm/wasm-bindgen/pull/4188)
+
+* Adding `getter`, `setter`, and `constructor` methods to enums now results in a compiler error. This was previously erroneously allowed and resulted in invalid JS code gen.
+ [#4278](https://github.com/rustwasm/wasm-bindgen/pull/4278)
+
+* Handle stuck and failed WebDriver processes when re-trying to start them.
+ [#4340](https://github.com/rustwasm/wasm-bindgen/pull/4340)
+
+* Align test output closer to native `cargo test`.
+ [#4358](https://github.com/rustwasm/wasm-bindgen/pull/4358)
+
+* Error if URL in `_REMOTE` can't be parsed instead of just ignoring it.
+ [#4362](https://github.com/rustwasm/wasm-bindgen/pull/4362)
+
+* Remove `WASM_BINDGEN_THREADS_MAX_MEMORY` and `WASM_BINDGEN_THREADS_STACK_SIZE`. The maximum memory size can be set via `-Clink-arg=--max-memory=`. The stack size of a thread can be set when initializing the thread via the `default` function.
+ [#4363](https://github.com/rustwasm/wasm-bindgen/pull/4363)
+
+* `console.*()` calls in tests are now always intercepted by default. To show them use `--nocapture`. When shown they are always printed in-place instead of after test results, analogous to `cargo test`.
+ [#4356](https://github.com/rustwasm/wasm-bindgen/pull/4356)
+
+### Fixed
+
+- Fixed using [JavaScript keyword](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#keywords) as identifiers not being handled correctly.
+ [#4329](https://github.com/rustwasm/wasm-bindgen/pull/4329)
+
+ - Using JS keywords as `struct` and `enum` names will now error at compile time, instead of causing invalid JS code gen.
+ - Using JS keywords that are not valid to call or access properties on will now error at compile time, instead of causing invalid JS code gen if used as:
+ 1. The first part of a `js_namespace` on imports.
+ 2. The name of an imported type or constant if the type or constant does not have a `js_namespace` or `module` attribute.
+ 3. The name of an imported function if the function is not a method and does not have a `js_namespace` or `module` attribute.
+ - Using JS keywords on imports in places other than the above will no longer cause the keywords to be escaped as `_{keyword}`.
+
+* Fixed passing large arrays into Rust failing because of internal memory allocations invalidating the memory buffer.
+ [#4353](https://github.com/rustwasm/wasm-bindgen/pull/4353)
+
+* Pass along an `ignore` attribute to `unsupported` tests.
+ [#4360](https://github.com/rustwasm/wasm-bindgen/pull/4360)
+
+* Use OS provided temporary directory for tests instead of Cargo's `target` directory.
+ [#4361](https://github.com/rustwasm/wasm-bindgen/pull/4361)
+
+* Error if URL in `_REMOTE` can't be parsed.
+ [#4362](https://github.com/rustwasm/wasm-bindgen/pull/4362)
+
+* Internal functions are now removed instead of invalidly imported if they are unused.
+ [#4366](https://github.com/rustwasm/wasm-bindgen/pull/4366)
+
+* Fixed `no_std` support for all APIs in `web-sys`.
+ [#4378](https://github.com/rustwasm/wasm-bindgen/pull/4378)
+
+* Prevent generating duplicate exports for closure conversions.
+ [#4380](https://github.com/rustwasm/wasm-bindgen/pull/4380)
+
+--------------------------------------------------------------------------------
+
+## [0.2.99](https://github.com/rustwasm/wasm-bindgen/compare/0.2.98...0.2.99)
+
+Released 2024-12-07
+
+### Fixed
+
+- Mark `wasm-bindgen` v0.2.98 only compatible with `wasm-bindgen-cli` of the same version.
+ [#4331](https://github.com/rustwasm/wasm-bindgen/pull/4331)
+
+--------------------------------------------------------------------------------
+
+## [0.2.98](https://github.com/rustwasm/wasm-bindgen/compare/0.2.97...0.2.98)
+
+Released 2024-12-07
+
+### Added
+
+* Add support for compiling with `atomics` for Node.js.
+ [#4318](https://github.com/rustwasm/wasm-bindgen/pull/4318)
+
+* Add `WASM_BINDGEN_TEST_DRIVER_TIMEOUT` environment variable to control the timeout to start and connect to the test driver.
+ [#4320](https://github.com/rustwasm/wasm-bindgen/pull/4320)
+
+* Add support for number slices of type `MaybeUninit`.
+ [#4316](https://github.com/rustwasm/wasm-bindgen/pull/4316)
+
+### Changed
+
+* Remove `once_cell/critical-section` requirement for `no_std` with atomics.
+ [#4322](https://github.com/rustwasm/wasm-bindgen/pull/4322)
+
+* `static FOO: Option` now returns `None` if undeclared in JS instead of throwing an error in JS.
+ [#4319](https://github.com/rustwasm/wasm-bindgen/pull/4319)
+
+### Fixed
+
+* Fix macro-hygiene for calls to `std::thread_local!`.
+ [#4315](https://github.com/rustwasm/wasm-bindgen/pull/4315)
+
+* Fix feature resolver version 1 compatibility.
+ [#4327](https://github.com/rustwasm/wasm-bindgen/pull/4327)
+
+--------------------------------------------------------------------------------
+
+## [0.2.97](https://github.com/rustwasm/wasm-bindgen/compare/0.2.96...0.2.97)
+
+Released 2024-11-30
+
+### Fixed
+
+* Fixed `js-sys` and `wasm-bindgen-futures` relying on internal paths of `wasm-bindgen` that are not crate feature additive.
+ [#4305](https://github.com/rustwasm/wasm-bindgen/pull/4305)
+
+--------------------------------------------------------------------------------
+
+## [0.2.96](https://github.com/rustwasm/wasm-bindgen/compare/0.2.95...0.2.96)
+
+Released 2024-11-29
+
+### Added
+
+* Added support for the [`HTMLOrSVGElement`](https://html.spec.whatwg.org/#htmlorsvgelement) `mixin`, which is used for all interfaces deriving from `Element`.
+ [#4143](https://github.com/rustwasm/wasm-bindgen/pull/4143)
+
+* Added bindings for [MathMLElement](https://www.w3.org/TR/MathML3).
+ [#4143](https://github.com/rustwasm/wasm-bindgen/pull/4143)
+
+* Added JSDoc type annotations to C-style enums.
+ [#4192](https://github.com/rustwasm/wasm-bindgen/pull/4192)
+
+* Added support for C-style enums with negative discriminants.
+ [#4204](https://github.com/rustwasm/wasm-bindgen/pull/4204)
+
+* Added bindings for `MediaStreamTrack.getCapabilities`.
+ [#4236](https://github.com/rustwasm/wasm-bindgen/pull/4236)
+
+* Added WASM ABI support for `u128` and `i128`
+ [#4222](https://github.com/rustwasm/wasm-bindgen/pull/4222)
+
+* Added support for the `wasm32v1-none` target.
+ [#4277](https://github.com/rustwasm/wasm-bindgen/pull/4277)
+
+* Added support for `no_std` to `js-sys`, `web-sys`, `wasm-bindgen-futures` and `wasm-bindgen-test`.
+ [#4277](https://github.com/rustwasm/wasm-bindgen/pull/4277)
+
+* Added support for `no_std` to `link_to!`, `static_string` (via `thread_local_v2`) and `throw`.
+ [#4277](https://github.com/rustwasm/wasm-bindgen/pull/4277)
+
+* Added environment variables to configure tests: `WASM_BINDGEN_USE_BROWSER`, `WASM_BINDGEN_USE_DEDICATED_WORKER`, `WASM_BINDGEN_USE_SHARED_WORKER` `WASM_BINDGEN_USE_SERVICE_WORKER`, `WASM_BINDGEN_USE_DENO` and `WASM_BINDGEN_USE_NODE_EXPERIMENTAL`. The use of `wasm_bindgen_test_configure!` will overwrite any environment variable.
+ [#4295](https://github.com/rustwasm/wasm-bindgen/pull/4295)
+
+### Changed
+
+* String enums now generate private TypeScript types but only if used.
+ [#4174](https://github.com/rustwasm/wasm-bindgen/pull/4174)
+
+* Remove unnecessary JSDoc type annotations from generated `.d.ts` files
+ [#4187](https://github.com/rustwasm/wasm-bindgen/pull/4187)
+
+* Deprecate `autofocus`, `tabIndex`, `focus()` and `blur()` bindings in favor of bindings on the inherited `Element` class.
+ [#4143](https://github.com/rustwasm/wasm-bindgen/pull/4143)
+
+* Optimized ABI performance for `Option<{i32,u32,isize,usize,f32,*const T,*mut T}>`.
+ [#4183](https://github.com/rustwasm/wasm-bindgen/pull/4183)
+
+* Deprecate `--reference-types` in favor of automatic target feature detection.
+ [#4237](https://github.com/rustwasm/wasm-bindgen/pull/4237)
+
+* `wasm-bindgen-test-runner` now tries to restart the WebDriver on failure, instead of spending its timeout period trying to connect to a non-existing WebDriver.
+ [#4267](https://github.com/rustwasm/wasm-bindgen/pull/4267)
+
+* Deprecated `#[wasm_bindgen(thread_local)]` in favor of `#[wasm_bindgen(thread_local_v2)]`, which creates a `wasm_bindgen::JsThreadLocal`. It is similar to `std::thread::LocalKey` but supports `no_std`.
+ [#4277](https://github.com/rustwasm/wasm-bindgen/pull/4277)
+
+* Updated the WebGPU API to the current draft as of 2024-11-22.
+ [#4290](https://github.com/rustwasm/wasm-bindgen/pull/4290)
+
+* Improved error messages for `self` arguments in invalid positions.
+ [#4276](https://github.com/rustwasm/wasm-bindgen/pull/4276)
+
+### Fixed
+
+* Fixed methods with `self: &Self` consuming the object.
+ [#4178](https://github.com/rustwasm/wasm-bindgen/pull/4178)
+
+* Fixed unused string enums generating JS values.
+ [#4193](https://github.com/rustwasm/wasm-bindgen/pull/4193)
+
+* Fixed triggering lints in testing facilities.
+ [#4195](https://github.com/rustwasm/wasm-bindgen/pull/4195)
+
+* Fixed `#[should_panic]` not working with `#[wasm_bindgen_test(unsupported = ...)]`.
+ [#4196](https://github.com/rustwasm/wasm-bindgen/pull/4196)
+
+* Fixed potential `null` error when using `JsValue::as_debug_string()`.
+ [#4192](https://github.com/rustwasm/wasm-bindgen/pull/4192)
+
+* Fixed generated types when the getter and setter of a property have different types.
+ [#4202](https://github.com/rustwasm/wasm-bindgen/pull/4202)
+
+* Fixed generated types when a static getter/setter has the same name as an instance getter/setter.
+ [#4202](https://github.com/rustwasm/wasm-bindgen/pull/4202)
+
+* Fixed invalid TypeScript return types for multivalue signatures.
+ [#4210](https://github.com/rustwasm/wasm-bindgen/pull/4210)
+
+* Only emit `table.fill` instructions if the bulk-memory proposal is enabled.
+ [#4237](https://github.com/rustwasm/wasm-bindgen/pull/4237)
+
+* Fixed calls to `JsCast::instanceof()` not respecting JavaScript namespaces.
+ [#4241](https://github.com/rustwasm/wasm-bindgen/pull/4241)
+
+* Fixed imports for functions using `this` and late binding.
+ [#4225](https://github.com/rustwasm/wasm-bindgen/pull/4225)
+
+* Don't expose non-functioning implicit constructors to classes when none are provided.
+ [#4282](https://github.com/rustwasm/wasm-bindgen/pull/4282)
+
+--------------------------------------------------------------------------------
+
+## [0.2.95](https://github.com/rustwasm/wasm-bindgen/compare/0.2.94...0.2.95)
+
+Released 2024-10-10
+
+### Added
+
+* Added support for implicit discriminants in enums.
+ [#4152](https://github.com/rustwasm/wasm-bindgen/pull/4152)
+
+* Added support for `Self` in complex type expressions in methods.
+ [#4155](https://github.com/rustwasm/wasm-bindgen/pull/4155)
+
+### Changed
+
+* String enums are no longer generate TypeScript types.
+ [#4174](https://github.com/rustwasm/wasm-bindgen/pull/4174)
+
+### Fixed
+
+* Fixed generated setters from WebIDL interface attributes binding to wrong JS method names.
+ [#4170](https://github.com/rustwasm/wasm-bindgen/pull/4170)
+
+* Fix string enums showing up in JS documentation and TypeScript bindings without corresponding types.
+ [#4175](https://github.com/rustwasm/wasm-bindgen/pull/4175)
+
+--------------------------------------------------------------------------------
+
+## [0.2.94](https://github.com/rustwasm/wasm-bindgen/compare/0.2.93...0.2.94) (YANKED)
+
+Released 2024-10-09
+
+### Added
+
+* Added support for the WebAssembly `Tail Call` proposal.
+ [#4111](https://github.com/rustwasm/wasm-bindgen/pull/4111)
+
+* Add bindings for `RTCPeerConnection.setConfiguration(RTCConfiguration)` method.
+ [#4105](https://github.com/rustwasm/wasm-bindgen/pull/4105)
+
+* Add bindings to `RTCRtpTransceiverDirection.stopped`.
+ [#4102](https://github.com/rustwasm/wasm-bindgen/pull/4102)
+
+* Added experimental support for `Symbol.dispose` via `WASM_BINDGEN_EXPERIMENTAL_SYMBOL_DISPOSE`.
+ [#4118](https://github.com/rustwasm/wasm-bindgen/pull/4118)
+
+* Added bindings for the draft [WebRTC Encoded Transform](https://www.w3.org/TR/webrtc-encoded-transform) spec.
+ [#4125](https://github.com/rustwasm/wasm-bindgen/pull/4125)
+
+* Added `Debug` implementation to `JsError`.
+ [#4136](https://github.com/rustwasm/wasm-bindgen/pull/4136)
+
+* Added support for `js_name` and `skip_typescript` attributes for string enums.
+ [#4147](https://github.com/rustwasm/wasm-bindgen/pull/4147)
+
+* Added `unsupported` crate to `wasm_bindgen_test(unsupported = test)` as a way of running tests on non-Wasm targets as well.
+ [#4150](https://github.com/rustwasm/wasm-bindgen/pull/4150)
+
+* Added additional bindings for methods taking buffer view types (e.g. `&[u8]`) with corresponding JS types (e.g. `Uint8Array`).
+ [#4156](https://github.com/rustwasm/wasm-bindgen/pull/4156)
+
+* Added additional bindings for setters from WebIDL interface attributes with applicaple parameter types of just `JsValue`.
+ [#4156](https://github.com/rustwasm/wasm-bindgen/pull/4156)
+
+### Changed
+
+* Implicitly enable reference type and multivalue transformations if the module already makes use of the corresponding target features.
+ [#4133](https://github.com/rustwasm/wasm-bindgen/pull/4133)
+
+* Updated Gamepad API.
+ [#4134](https://github.com/rustwasm/wasm-bindgen/pull/4134)
+
+* Deprecated `Gamepad::display_id` and `GamepadHapticActuator::type_`.
+ [#4134](https://github.com/rustwasm/wasm-bindgen/pull/4134)
+
+* Removed `GamepadAxisMoveEvent`, `GamepadAxisMoveEventInit`, `GamepadButtonEvent`, `GamepadButtonEventInit` and `GamepadServiceTest`, which were seemingly never implemented by any JS environment.
+ [#4134](https://github.com/rustwasm/wasm-bindgen/pull/4134)
+
+* Changed `TextDecoder.decode()` `input` parameter type from `&mut [u8]` to `&[u8]`.
+ [#4141](https://github.com/rustwasm/wasm-bindgen/pull/4141)
+
+* Updated the WebGPU API to the current draft as of 2024-10-07.
+ [#4145](https://github.com/rustwasm/wasm-bindgen/pull/4145)
+
+* Deprecated generated setters from WebIDL interface attribute taking `JsValue` in favor of newer bindings with specific parameter types.
+ [#4156](https://github.com/rustwasm/wasm-bindgen/pull/4156)
+
+### Fixed
+
+* Fixed linked modules emitting snippet files when not using `--split-linked-modules`.
+ [#4066](https://github.com/rustwasm/wasm-bindgen/pull/4066)
+
+* Fixed incorrect deprecation warning when passing no parameter into `default()` (`init()`) or `initSync()`.
+ [#4074](https://github.com/rustwasm/wasm-bindgen/pull/4074)
+
+* Fixed many proc-macro generated `impl` blocks missing `#[automatically_derived]`, affecting test coverage.
+ [#4078](https://github.com/rustwasm/wasm-bindgen/pull/4078)
+
+* Fixed negative `BigInt` values being incorrectly formatted with two minus signs.
+ [#4082](https://github.com/rustwasm/wasm-bindgen/pull/4082)
+ [#4088](https://github.com/rustwasm/wasm-bindgen/pull/4088)
+
+* Fixed emitted `package.json` structure to correctly specify its dependencies
+ [#4091](https://github.com/rustwasm/wasm-bindgen/pull/4091)
+
+* Fixed returning `Option` now correctly has the `| undefined` type in TS bindings.
+ [#4137](https://github.com/rustwasm/wasm-bindgen/pull/4137)
+
+* Fixed enum variant name collisions with object prototype fields.
+ [#4137](https://github.com/rustwasm/wasm-bindgen/pull/4137)
+
+* Fixed multiline doc comment alignment and remove empty ones entirely.
+ [#4135](https://github.com/rustwasm/wasm-bindgen/pull/4135)
+
+* Fixed `experimental-nodejs-module` target when used with `#[wasm_bindgen(start)]`.
+ [#4093](https://github.com/rustwasm/wasm-bindgen/pull/4093)
+
+* Fixed error when importing very large JS files.
+ [#4146](https://github.com/rustwasm/wasm-bindgen/pull/4146)
+
+* Specify `"type": "module"` when deploying to nodejs-module
+ [#4092](https://github.com/rustwasm/wasm-bindgen/pull/4092)
+
+* Fixed string enums not generating TypeScript types.
+ [#4147](https://github.com/rustwasm/wasm-bindgen/pull/4147)
+
+* Bindings that take buffer view types (e.g. `&[u8]`) as parameters will now correctly return a `Result` when they might not support a backing `SharedArrayBuffer`. This only applies to new and unstable APIs, which won't cause a breaking in the API.
+ [#4156](https://github.com/rustwasm/wasm-bindgen/pull/4156)
+
+--------------------------------------------------------------------------------
+
+## [0.2.93](https://github.com/rustwasm/wasm-bindgen/compare/0.2.92...0.2.93)
+
+Released 2024-08-13
+
+### Added
+
+* Allow exporting functions named `default`. Throw error in wasm-bindgen-cli if --target web and
+ an exported symbol is named `default`.
+ [#3930](https://github.com/rustwasm/wasm-bindgen/pull/3930)
+
+* Added support for arbitrary expressions when using `#[wasm_bindgen(typescript_custom_section)]`.
+ [#3901](https://github.com/rustwasm/wasm-bindgen/pull/3901)
+
+* Implement `From>` for `JsValue`.
+ [#3877](https://github.com/rustwasm/wasm-bindgen/pull/3877)
+
+* Add method `copy_within` for TypedArray, add methods `find_last`,`find_last_index` for Array.
+ [#3888](https://github.com/rustwasm/wasm-bindgen/pull/3888)
+
+* Added support for returning `Vec`s from async functions.
+ [#3630](https://github.com/rustwasm/wasm-bindgen/pull/3630)
+
+* Added bindings for `InputDeviceInfo` and `MediaTrackCapabilities`.
+ [#3935](https://github.com/rustwasm/wasm-bindgen/pull/3935)
+
+* Add bindings for `RTCRtpReceiver.getCapabilities(DOMString)` method.
+ [#3941](https://github.com/rustwasm/wasm-bindgen/pull/3941)
+
+* Add bindings for `VisualViewport`.
+ [#3931](https://github.com/rustwasm/wasm-bindgen/pull/3931)
+
+* Add bindings for `queueMicrotask`.
+ [#3981](https://github.com/rustwasm/wasm-bindgen/pull/3981)
+
+* Add experimental bindings for User Agent Client Hints API
+ [#3989](https://github.com/rustwasm/wasm-bindgen/pull/3989)
+
+* Add bindings for `FocusOptions`.
+ [#3996](https://github.com/rustwasm/wasm-bindgen/pull/3996)
+
+* Add bindings for `RTCRtpReceiver.jitterBufferTarget`.
+ [#3968](https://github.com/rustwasm/wasm-bindgen/pull/3968)
+
+* Generate getters for all WebIDL dictionary types.
+ [#3993](https://github.com/rustwasm/wasm-bindgen/pull/3993)
+
+* Support for iterable in WebIDL. Gives `entries`, `keys`, `values` methods for regular and asynchronous, as well as `for_each` for regular, iterables.
+ [#3962](https://github.com/rustwasm/wasm-bindgen/pull/3962)
+
+* Add bindings for `HTMLTableCellElement.abbr` and `scope` properties.
+ [#3972](https://github.com/rustwasm/wasm-bindgen/pull/3972)
+
+* Add WebIDL definitions relating to `Popover API`.
+ [#3977](https://github.com/rustwasm/wasm-bindgen/pull/3977)
+
+* Added the `thread_stack_size` property to the object parameter of `default()` (`init()`) and `initSync()`, making it possible to set the stack size of spawned threads. `__wbindgen_thread_destroy()` now has a third optional parameter for the stack size, the default stack size is assumed when not passing it. When calling from the thread to be destroyed, by passing no parameters, the correct stack size is determined internally.
+ [#3995](https://github.com/rustwasm/wasm-bindgen/pull/3995)
+
+* Added bindings to the Device Memory API.
+ [#4011](https://github.com/rustwasm/wasm-bindgen/pull/4011)
+
+* Added support for WebIDL records. This added new methods to various APIs, notably `ClipboardItem()`, `GPUDeviceDescriptor.requiredLimits` and `Header()`.
+ [#4030](https://github.com/rustwasm/wasm-bindgen/pull/4030)
+
+* Added an official MSRV policy. Library MSRV changes will be accompanied by a minor version bump. CLI tool MSRV can change with any version bump.
+ [#4038](https://github.com/rustwasm/wasm-bindgen/pull/4038)
+
+* Added bindings to `NavigatorOptions.vibrate`.
+ [#4041](https://github.com/rustwasm/wasm-bindgen/pull/4041)
+
+* Added an experimental Node.JS ES module target, in comparison the current `node` target uses CommonJS, with `--target experimental-nodejs-module` or when testing with `wasm_bindgen_test_configure!(run_in_node_experimental)`.
+ [#4027](https://github.com/rustwasm/wasm-bindgen/pull/4027)
+
+* Added importing strings as `JsString` through `#[wasm_bindgen(thread_local, static_string)] static STRING: JsString = "a string literal";`.
+ [#4055](https://github.com/rustwasm/wasm-bindgen/pull/4055)
+
+* Added experimental test coverage support for `wasm-bindgen-test-runner`, see the guide for more information.
+ [#4060](https://github.com/rustwasm/wasm-bindgen/pull/4060)
+
+### Changed
+
+* Stabilize Web Share API.
+ [#3882](https://github.com/rustwasm/wasm-bindgen/pull/3882)
+
+* Generate JS bindings for WebIDL dictionary setters instead of using `Reflect`. This increases the size of the Web API bindings but should be more performant. Also, importing getters/setters from JS now supports specifying the JS attribute name as a string, e.g. `#[wasm_bindgen(method, setter = "x-cdm-codecs")]`.
+ [#3898](https://github.com/rustwasm/wasm-bindgen/pull/3898)
+
+* Greatly improve the performance of sending WebIDL 'string enums' across the JavaScript boundary by converting the enum variant string to/from an int.
+ [#3915](https://github.com/rustwasm/wasm-bindgen/pull/3915)
+
+* Use `table.fill` when appropriate.
+ [#3446](https://github.com/rustwasm/wasm-bindgen/pull/3446)
+
+* Annotated methods in WebCodecs that throw.
+ [#3970](https://github.com/rustwasm/wasm-bindgen/pull/3970)
+
+* Update and stabilize the Clipboard API.
+ [#3992](https://github.com/rustwasm/wasm-bindgen/pull/3992)
+
+* Deprecate builder-pattern type setters for WebIDL dictionary types and introduce non-mutable setters instead.
+ [#3993](https://github.com/rustwasm/wasm-bindgen/pull/3993)
+
+* Allow imported async functions to return any type that can be converted from a `JsValue`.
+ [#3919](https://github.com/rustwasm/wasm-bindgen/pull/3919)
+
+* Update Web Authentication API to level 3.
+ [#4000](https://github.com/rustwasm/wasm-bindgen/pull/4000)
+
+* Deprecate `AudioBufferSourceNode.onended` and `AudioBufferSourceNode.stop()`.
+ [#4020](https://github.com/rustwasm/wasm-bindgen/pull/4020)
+
+* Increase default stack size for spawned threads from 1 to 2 MB.
+ [#3995](https://github.com/rustwasm/wasm-bindgen/pull/3995)
+
+* Deprecated parameters to `default` (`init`) and `initSync` in favor of an object.
+ [#3995](https://github.com/rustwasm/wasm-bindgen/pull/3995)
+
+* Update `AbortSignal` and `AbortController` according to the WHATWG specification.
+ [#4026](https://github.com/rustwasm/wasm-bindgen/pull/4026)
+
+* Update the Indexed DB API.
+ [#4027](https://github.com/rustwasm/wasm-bindgen/pull/4027)
+
+* `UnwrapThrowExt for Result` now makes use of the required `Debug` bound to display the error as well.
+ [#4035](https://github.com/rustwasm/wasm-bindgen/pull/4035)
+ [#4049](https://github.com/rustwasm/wasm-bindgen/pull/4049)
+
+* MSRV of CLI tools bumped to v1.76. This does not affect libraries like `wasm-bindgen`, `js-sys` and `web-sys`!
+ [#4037](https://github.com/rustwasm/wasm-bindgen/pull/4037)
+
+* Filtered files in published crates, significantly reducing the package size and notably excluding any bash files.
+ [#4046](https://github.com/rustwasm/wasm-bindgen/pull/4046)
+
+* Deprecated `JsStatic` in favor of `#[wasm_bindgen(thread_local)]`, which creates a `std::thread::LocalKey`. The syntax is otherwise the same.
+ [#4057](https://github.com/rustwasm/wasm-bindgen/pull/4057)
+
+* Removed `impl Deref for JsStatic` when compiling with `cfg(target_feature = "atomics")`, which was unsound.
+ [#4057](https://github.com/rustwasm/wasm-bindgen/pull/4057)
+
+* Updated the WebGPU WebIDL to the current draft as of 2024-08-05.
+ [#4062](https://github.com/rustwasm/wasm-bindgen/pull/4062)
+
+* Use object URLs for linked modules without `--split-linked-modules`.
+ [#4067](https://github.com/rustwasm/wasm-bindgen/pull/4067)
+
+### Fixed
+
+* Copy port from headless test server when using `WASM_BINDGEN_TEST_ADDRESS`.
+ [#3873](https://github.com/rustwasm/wasm-bindgen/pull/3873)
+
+* Fix `catch` not being thread-safe.
+ [#3879](https://github.com/rustwasm/wasm-bindgen/pull/3879)
+
+* Fix MSRV compilation.
+ [#3927](https://github.com/rustwasm/wasm-bindgen/pull/3927)
+
+* Fix `clippy::empty_docs` lint.
+ [#3946](https://github.com/rustwasm/wasm-bindgen/pull/3946)
+
+* Fix missing target features in module when enabling reference types or multi-value transformation.
+ [#3967](https://github.com/rustwasm/wasm-bindgen/pull/3967)
+
+* Fixed Rust values getting GC'd while still borrowed.
+ [#3940](https://github.com/rustwasm/wasm-bindgen/pull/3940)
+
+* Fixed Rust values not getting GC'd if they were created via. a constructor.
+ [#3940](https://github.com/rustwasm/wasm-bindgen/pull/3940)
+
+* Fix triggering `clippy::mem_forget` lint in exported structs.
+ [#3985](https://github.com/rustwasm/wasm-bindgen/pull/3985)
+
+* Fix MDN links to static interface methods.
+ [#4010](https://github.com/rustwasm/wasm-bindgen/pull/4010)
+
+* Fixed Deno support.
+ [#3990](https://github.com/rustwasm/wasm-bindgen/pull/3990)
+
+* Fix `__wbindgen_thread_destroy()` ignoring parameters.
+ [#3995](https://github.com/rustwasm/wasm-bindgen/pull/3995)
+
+* Fix `no_std` support and therefor compiling with `default-features = false`.
+ [#4005](https://github.com/rustwasm/wasm-bindgen/pull/4005)
+
+* Fix byte order for big-endian platforms.
+ [#4015](https://github.com/rustwasm/wasm-bindgen/pull/4015)
+
+* Allow ex/importing structs, functions and parameters named with raw identifiers.
+ [#4025](https://github.com/rustwasm/wasm-bindgen/pull/4025)
+
+* Implement a more reliable way to detect the stack pointer.
+ [#4036](https://github.com/rustwasm/wasm-bindgen/pull/4036)
+
+* `#[track_caller]` is now always applied on `UnwrapThrowExt` methods when not targeting `wasm32-unknown-unknown`.
+ [#4042](https://github.com/rustwasm/wasm-bindgen/pull/4042)
+
+* Fixed linked modules emitting snippet files when not using `--split-linked-modules`.
+ [#4066](https://github.com/rustwasm/wasm-bindgen/pull/4066)
+
+--------------------------------------------------------------------------------
+
+## [0.2.92](https://github.com/rustwasm/wasm-bindgen/compare/0.2.91...0.2.92)
+
+Released 2024-03-04
+
+### Added
+
+* Add bindings for `RTCPeerConnectionIceErrorEvent`.
+ [#3835](https://github.com/rustwasm/wasm-bindgen/pull/3835)
+
+* Add bindings for `CanvasState.reset()`, affecting `CanvasRenderingContext2D` and `OffscreenCanvasRenderingContext2D`.
+ [#3844](https://github.com/rustwasm/wasm-bindgen/pull/3844)
+
+* Add `TryFrom` implementations for `Number`, that allow losslessly converting from 64- and 128-bits numbers.
+ [#3847](https://github.com/rustwasm/wasm-bindgen/pull/3847)
+
+* Add support for `Option<*const T>`, `Option<*mut T>` and `NonNull`.
+ [#3852](https://github.com/rustwasm/wasm-bindgen/pull/3852)
+ [#3857](https://github.com/rustwasm/wasm-bindgen/pull/3857)
+
+* Allow overriding the URL used for headless tests by setting `WASM_BINDGEN_TEST_ADDRESS`.
+ [#3861](https://github.com/rustwasm/wasm-bindgen/pull/3861)
+
+### Fixed
+
+* Make .wasm output deterministic when using `--reference-types`.
+ [#3851](https://github.com/rustwasm/wasm-bindgen/pull/3851)
+
+* Don't allow invalid Unicode scalar values in `char`.
+ [#3866](https://github.com/rustwasm/wasm-bindgen/pull/3866)
+
+--------------------------------------------------------------------------------
+
+## [0.2.91](https://github.com/rustwasm/wasm-bindgen/compare/0.2.90...0.2.91)
+
+Released 2024-02-06
+
+### Added
+
+* Added bindings for the `RTCRtpTransceiver.setCodecPreferences()` and unstable bindings for the `RTCRtpEncodingParameters.scalabilityMode`.
+ [#3828](https://github.com/rustwasm/wasm-bindgen/pull/3828)
+
+* Add unstable bindings for the FileSystemAccess API
+ [#3810](https://github.com/rustwasm/wasm-bindgen/pull/3810)
+
+* Added support for running tests in shared and service workers with `wasm_bindgen_test_configure!` `run_in_shared_worker` and `run_in_service_worker`.
+ [#3804](https://github.com/rustwasm/wasm-bindgen/pull/3804)
+
+* Accept the `--skip` flag with `wasm-bindgen-test-runner`.
+ [#3803](https://github.com/rustwasm/wasm-bindgen/pull/3803)
+
+* Introduce environment variable `WASM_BINDGEN_TEST_NO_ORIGIN_ISOLATION` to disable origin isolation for `wasm-bindgen-test-runner`.
+ [#3807](https://github.com/rustwasm/wasm-bindgen/pull/3807)
+
+* Add bindings for `USBDevice.forget()`.
+ [#3821](https://github.com/rustwasm/wasm-bindgen/pull/3821)
+
+### Changed
+
+* Stabilize `ClipboardEvent`.
+ [#3791](https://github.com/rustwasm/wasm-bindgen/pull/3791)
+
+* Use immutable buffers in `SubtleCrypto` methods.
+ [#3797](https://github.com/rustwasm/wasm-bindgen/pull/3797)
+
+* Deprecate `wasm_bindgen_test_configure!`s `run_in_worker` in favor of `run_in_dedicated_worker`.
+ [#3804](https://github.com/rustwasm/wasm-bindgen/pull/3804)
+
+* Updated the WebGPU WebIDL to the current draft as of 2024-01-30. Note that this retains the previous update's workaround for `GPUPipelineError`, and holds back an update to the `buffer` argument of the `GPUQueue.{writeBuffer,writeTexture}` methods.
+ [#3816](https://github.com/rustwasm/wasm-bindgen/pull/3816)
+
+* Deprecate `--weak-refs` and `WASM_BINDGEN_WEAKREF` in favor of automatic run-time detection.
+ [#3822](https://github.com/rustwasm/wasm-bindgen/pull/3822)
+
+### Fixed
+
+* Fixed UB when freeing strings received from JS if not using the default allocator.
+ [#3808](https://github.com/rustwasm/wasm-bindgen/pull/3808)
+
+* Fixed temporary folder detection by `wasm-bindgen-test-runner` on MacOS.
+ [#3817](https://github.com/rustwasm/wasm-bindgen/pull/3817)
+
+* Fixed using `#[wasm_bindgen(js_name = default)]` with `#[wasm_bindgen(module = ...)]`.
+ [#3823](https://github.com/rustwasm/wasm-bindgen/pull/3823)
+
+* Fixed nightly build of `wasm-bindgen-futures`.
+ [#3827](https://github.com/rustwasm/wasm-bindgen/pull/3827)
+
+--------------------------------------------------------------------------------
+
+## [0.2.90](https://github.com/rustwasm/wasm-bindgen/compare/0.2.89...0.2.90)
+
+Released 2024-01-06
+
+### Fixed
+
+* Fix JS shim default path detection for the no-modules target.
+ [#3748](https://github.com/rustwasm/wasm-bindgen/pull/3748)
+
+### Added
+
+* Add bindings for `HTMLFormElement.requestSubmit()`.
+ [#3747](https://github.com/rustwasm/wasm-bindgen/pull/3747)
+
+* Add bindings for `RTCRtpSender.getCapabilities(DOMString)` method, `RTCRtpCapabilities`, `RTCRtpCodecCapability` and `RTCRtpHeaderExtensionCapability`.
+ [#3737](https://github.com/rustwasm/wasm-bindgen/pull/3737)
+
+* Add bindings for `UserActivation`.
+ [#3719](https://github.com/rustwasm/wasm-bindgen/pull/3719)
+
+* Add unstable bindings for the Compression Streams API.
+ [#3752](https://github.com/rustwasm/wasm-bindgen/pull/3752)
+
+### Changed
+
+* Stabilize File System API.
+ [#3745](https://github.com/rustwasm/wasm-bindgen/pull/3745)
+
+* Stabilize `QueuingStrategy`.
+ [#3753](https://github.com/rustwasm/wasm-bindgen/pull/3753)
+
+### Fixed
+
+* Fixed a compiler error when using `#[wasm_bindgen]` inside `macro_rules!`.
+ [#3725](https://github.com/rustwasm/wasm-bindgen/pull/3725)
+
+### Removed
+
+* Removed Gecko-only `InstallTriggerData` and Gecko-internal `FlexLineGrowthState`, `GridDeclaration`, `GridTrackState`,
+ `RtcLifecycleEvent` and `WebrtcGlobalStatisticsReport` features.
+ [#3723](https://github.com/rustwasm/wasm-bindgen/pull/3723)
+
+--------------------------------------------------------------------------------
+
+## [0.2.89](https://github.com/rustwasm/wasm-bindgen/compare/0.2.88...0.2.89)
+
+Released 2023-11-27.
+
+### Added
+
+* Add additional constructor to `DataView` for `SharedArrayBuffer`.
+ [#3695](https://github.com/rustwasm/wasm-bindgen/pull/3695)
+
+* Stabilize `wasm_bindgen::module()`.
+ [#3690](https://github.com/rustwasm/wasm-bindgen/pull/3690)
+
+### Fixed
+
+* The DWARF section is now correctly modified instead of leaving it in a broken state.
+ [#3483](https://github.com/rustwasm/wasm-bindgen/pull/3483)
+
+* Fixed an issue where `#[wasm_bindgen]` automatically derived the `TryFrom` trait for any struct, preventing custom `TryFrom` implementations. It has been updated to utilize a new `TryFromJsValue` trait instead.
+ [#3709](https://github.com/rustwasm/wasm-bindgen/pull/3709)
+
+* Update the TypeScript signature of `__wbindgen_thread_destroy` to indicate that it's parameters are optional.
+ [#3703](https://github.com/rustwasm/wasm-bindgen/pull/3703)
+
+### Removed
+
+* Removed Gecko-internal dictionary bindings `Csp`, `CspPolicies`, `CspReport` and `CspReportProperties`.
+ [#3721](https://github.com/rustwasm/wasm-bindgen/pull/3721)
+
+--------------------------------------------------------------------------------
+
+## [0.2.88](https://github.com/rustwasm/wasm-bindgen/compare/0.2.87...0.2.88) (YANKED)
+
+Released 2023-11-01
+
+### Added
+
+* Add bindings for `RTCRtpTransceiverInit.sendEncodings`.
+ [#3642](https://github.com/rustwasm/wasm-bindgen/pull/3642)
+
+* Add bindings for the Web Locks API to `web-sys`.
+ [#3604](https://github.com/rustwasm/wasm-bindgen/pull/3604)
+
+* Add bindings for `ViewTransition` to `web-sys`.
+ [#3598](https://github.com/rustwasm/wasm-bindgen/pull/3598)
+
+* Extend `AudioContext` with unstable features supporting audio sink configuration.
+ [#3433](https://github.com/rustwasm/wasm-bindgen/pull/3433)
+
+* Add bindings for `WebAssembly.Tag` and `WebAssembly.Exception`.
+ [#3484](https://github.com/rustwasm/wasm-bindgen/pull/3484)
+
+* Re-export `wasm-bindgen` from `js-sys`, `web-sys` and `wasm-bindgen-futures`.
+ [#3466](https://github.com/rustwasm/wasm-bindgen/pull/3466)
+ [#3601](https://github.com/rustwasm/wasm-bindgen/pull/3601)
+
+* Re-export `js-sys` from `web-sys` and `wasm-bindgen-futures`.
+ [#3466](https://github.com/rustwasm/wasm-bindgen/pull/3466)
+ [#3601](https://github.com/rustwasm/wasm-bindgen/pull/3601)
+
+* Add bindings for async variants of `Atomics.wait`.
+ [#3504](https://github.com/rustwasm/wasm-bindgen/pull/3504)
+
+* Add bindings for `WorkerGlobalScope.performance`.
+ [#3506](https://github.com/rustwasm/wasm-bindgen/pull/3506)
+
+* Add support for installing pre-built artifacts of `wasm-bindgen-cli`
+ via `cargo binstall wasm-bindgen-cli`.
+ [#3544](https://github.com/rustwasm/wasm-bindgen/pull/3544)
+
+* Add bindings for `RTCDataChannel.id`.
+ [#3547](https://github.com/rustwasm/wasm-bindgen/pull/3547)
+
+* Add bindings for `HTMLElement.inert`.
+ [#3557](https://github.com/rustwasm/wasm-bindgen/pull/3557)
+
+* Add unstable bindings for the Prioritized Task Scheduling API.
+ [#3566](https://github.com/rustwasm/wasm-bindgen/pull/3566)
+
+* Add bindings for `CssStyleSheet` constructor and `replace(_sync)` methods.
+ [#3573](https://github.com/rustwasm/wasm-bindgen/pull/3573)
+
+* Add bindings for `CanvasTransform.setTransform(DOMMatrix2DInit)`.
+ [#3580](https://github.com/rustwasm/wasm-bindgen/pull/3580)
+
+* Add a `crate` attribute to the `wasm_bindgen_test` proc-macro to specify a
+ non-default path to the `wasm-bindgen-test` crate.
+ [#3593](https://github.com/rustwasm/wasm-bindgen/pull/3593)
+
+* Add support for passing `Vec`s of exported Rust types and strings to/from JS.
+ [#3554](https://github.com/rustwasm/wasm-bindgen/pull/3554)
+
+* Implement `TryFrom` for exported Rust types and strings.
+ [#3554](https://github.com/rustwasm/wasm-bindgen/pull/3554)
+
+* Handle the `#[ignore = "reason"]` attribute with the `wasm_bindgen_test`
+ proc-macro and accept the `--include-ignored` flag with `wasm-bindgen-test-runner`.
+ [#3644](https://github.com/rustwasm/wasm-bindgen/pull/3644)
+
+* Added missing additions to the Notification API.
+ [#3667](https://github.com/rustwasm/wasm-bindgen/pull/3667)
+
+### Changed
+
+* Updated the WebGPU WebIDL.
+ The optional `message` argument of [`GPUPipelineError`](https://www.w3.org/TR/webgpu/#gpupipelineerror)'s constructor has been manually specified as a required argument,
+ because required arguments occurring after optional arguments are currently not supported by the generator.
+ [#3480](https://github.com/rustwasm/wasm-bindgen/pull/3480)
+
+* Replaced `curl` with `ureq`. By default we now use Rustls instead of OpenSSL.
+ [#3511](https://github.com/rustwasm/wasm-bindgen/pull/3511)
+
+* Changed mutability of the argument `buffer` in `write` functions to immutable for `FileSystemSyncAccessHandle` and `FileSystemWritableFileStream`.
+ It was also automatically changed for `IdbFileHandle`, which is deprecated.
+ [#3537](https://github.com/rustwasm/wasm-bindgen/pull/3537)
+
+* Changed behavior when compiling to `wasm32-wasi` to match `wasm32-emscripten` and
+ non-Wasm targets, generating a stub that panics when called rather than a wasm-
+ bindgen placeholder.
+ [#3233](https://github.com/rustwasm/wasm-bindgen/pull/3233)
+
+* Changed constructor implementation in generated JS bindings, it is now
+ possible to override methods from generated JS classes using inheritance.
+ When exported constructors return `Self`.
+ [#3562](https://github.com/rustwasm/wasm-bindgen/pull/3562)
+
+* Made `wasm-bindgen` forwards-compatible with the standard C ABI.
+ [#3595](https://github.com/rustwasm/wasm-bindgen/pull/3595)
+
+* Changed the design of the internal `WasmAbi` trait. Rather than marking a type
+ which can be passed directly as a parameter/result to/from JS, it now lets
+ types specify how they can be split into / recreated from multiple primitive
+ types which are then passed to/from JS.
+ `WasmPrimitive` now serves the old function of `WasmAbi`, minus allowing
+ `#[repr(C)]` types.
+ [#3595](https://github.com/rustwasm/wasm-bindgen/pull/3595)
+
+* Use `queueMicrotask` in `wasm-bindgen-futures` for scheduling tasks on the next tick.
+ If that is not available, use the previous `Promise.then` mechanism as a fallback.
+ This should avoid quirks, like exceptions thrown get now properly reported
+ as normal exceptions rather than as rejected promises.
+ [#3611](https://github.com/rustwasm/wasm-bindgen/pull/3611)
+
+* Improved TypeScript bindings to accurately reference Rust enum types in function signatures,
+ enhancing type safety and compatibility.
+ [#3647](https://github.com/rustwasm/wasm-bindgen/pull/3647)
+
+* Throw an error on enum name collisions, previously only one enum would be emitted.
+ [#3669](https://github.com/rustwasm/wasm-bindgen/pull/3669)
+
+### Fixed
+
+* Fixed `wasm_bindgen` macro to handle raw identifiers in field names.
+ [#3621](https://github.com/rustwasm/wasm-bindgen/pull/3621)
+
+* Fixed bindings and comments for `Atomics.wait`.
+ [#3509](https://github.com/rustwasm/wasm-bindgen/pull/3509)
+
+* Fixed `wasm_bindgen_test` macro to handle raw identifiers in test names.
+ [#3541](https://github.com/rustwasm/wasm-bindgen/pull/3541)
+
+* Fixed Cargo license field to follow the SPDX 2.1 license expression standard.
+ [#3529](https://github.com/rustwasm/wasm-bindgen/pull/3529)
+
+* Use fully qualified paths in the `wasm_bindgen_test` macro.
+ [#3549](https://github.com/rustwasm/wasm-bindgen/pull/3549)
+
+* Fixed bug allowing JS primitives to be returned from exported constructors.
+ [#3562](https://github.com/rustwasm/wasm-bindgen/pull/3562)
+
+* Fixed optional parameters in JSDoc.
+ [#3577](https://github.com/rustwasm/wasm-bindgen/pull/3577)
+
+* Use re-exported `js-sys` from `wasm-bindgen-futures` to account for
+ non-default path specified by the `crate` attribute in `wasm_bindgen_futures`
+ proc-macro.
+ [#3601](https://github.com/rustwasm/wasm-bindgen/pull/3601)
+
+* Fix bug with function arguments coming from `macro_rules!`.
+ [#3625](https://github.com/rustwasm/wasm-bindgen/pull/3625)
+
+* Fix some calls to `free()` missing alignment.
+ [#3639](https://github.com/rustwasm/wasm-bindgen/pull/3639)
+
+* Fix wrong ABI for raw pointers.
+ [#3655](https://github.com/rustwasm/wasm-bindgen/pull/3655)
+
+### Removed
+
+* Removed `ReadableStreamByobReader::read_with_u8_array()` because it doesn't work with Wasm.
+ [#3582](https://github.com/rustwasm/wasm-bindgen/pull/3582)
+
+* Removed `GetNotificationOptions`, `NotificationBehavior` and `Notification.get()` because
+ they don't exist anymore.
+
+--------------------------------------------------------------------------------
+
+## [0.2.87](https://github.com/rustwasm/wasm-bindgen/compare/0.2.86...0.2.87)
+
+Released 2023-06-12.
+
+### Added
+
+* Implemented `IntoIterator` for `Array`.
+ [#3477](https://github.com/rustwasm/wasm-bindgen/pull/3477)
+
+### Changed
+
+* Deprecate `HtmlMenuItemElement` and parts of `HtmlMenuElement`.
+ [#3448](https://github.com/rustwasm/wasm-bindgen/pull/3448)
+
+* Stabilize `ResizeObserver`.
+ [#3459](https://github.com/rustwasm/wasm-bindgen/pull/3459)
+
+### Fixed
+
+* Take alignment into consideration during (de/re)allocation.
+ [#3463](https://github.com/rustwasm/wasm-bindgen/pull/3463)
+
+--------------------------------------------------------------------------------
+
+## 0.2.86
+
+Released 2023-05-16.
+
+[changes](https://github.com/rustwasm/wasm-bindgen/compare/0.2.85...0.2.86)
+
+--------------------------------------------------------------------------------
+
+## 0.2.85
+
+Released 2023-05-09.
+
+[changes](https://github.com/rustwasm/wasm-bindgen/compare/0.2.84...0.2.85)
+
+--------------------------------------------------------------------------------
+
+## 0.2.84
+
+Released 2023-02-01.
+
+[changes](https://github.com/rustwasm/wasm-bindgen/compare/0.2.83...0.2.84)
+
+--------------------------------------------------------------------------------
+
+## 0.2.83
+
+Released 2022-09-12.
+
+[changes](https://github.com/rustwasm/wasm-bindgen/compare/0.2.82...0.2.83)
+
+--------------------------------------------------------------------------------
+
+## 0.2.82
+
+Released 2022-07-25.
+
+[changes](https://github.com/rustwasm/wasm-bindgen/compare/0.2.81...0.2.82)
+
+--------------------------------------------------------------------------------
+
+## 0.2.81
+
+Released 2022-06-14.
+
+[changes](https://github.com/rustwasm/wasm-bindgen/compare/0.2.80...0.2.81)
+
+--------------------------------------------------------------------------------
+
## 0.2.80
Released 2022-04-04.
@@ -317,7 +1294,7 @@ Released 2020-04-29.
[#2099](https://github.com/rustwasm/wasm-bindgen/pull/2099)
* The output of `wasm-bindgen` is now compatible with Webpack 5 and the updated
- version of the wasm ESM integration specification.
+ version of the Wasm ESM integration specification.
[#2110](https://github.com/rustwasm/wasm-bindgen/pull/2099)
--------------------------------------------------------------------------------
@@ -411,7 +1388,7 @@ Released 2020-03-03.
tightended up a bit.
[#1987](https://github.com/rustwasm/wasm-bindgen/pull/1987)
-* The `self` identifier is no longe used on the `no-modules` target, making it a
+* The `self` identifier is no longer used on the `no-modules` target, making it a
bit more flexible in more environments.
[#1995](https://github.com/rustwasm/wasm-bindgen/pull/1995)
@@ -500,7 +1477,7 @@ Released 2019-11-19.
* Running `wasm-bindgen` over empty anyref modules now works again.
[#1861](https://github.com/rustwasm/wasm-bindgen/pull/1861)
-* Support for multi-value JS engines has been fixed as a wasm interface types
+* Support for multi-value JS engines has been fixed as a Wasm interface types
polyfill.
[#1863](https://github.com/rustwasm/wasm-bindgen/pull/1863)
@@ -654,7 +1631,7 @@ Released 2019-08-14.
* Add binding for `Element.getElementsByClassName`.
[#1665](https://github.com/rustwasm/wasm-bindgen/pull/1665)
-* `PartialEq` and `Eq` are now implementd for all `web-sys` types.
+* `PartialEq` and `Eq` are now implemented for all `web-sys` types.
[#1673](https://github.com/rustwasm/wasm-bindgen/pull/1673)
* The `wasm-bindgen-futures` crate now has support for futures when the
@@ -723,8 +1700,8 @@ Released 2019-07-11.
slices.
[#1639](https://github.com/rustwasm/wasm-bindgen/pull/1639)
-* When using the `bundler` target the import of the wasm file now uses the
- `.wasm` extension to ensure a wasm file is loaded.
+* When using the `bundler` target the import of the Wasm file now uses the
+ `.wasm` extension to ensure a Wasm file is loaded.
[#1646](https://github.com/rustwasm/wasm-bindgen/pull/1646)
* The old internal `Stack` trait has been removed since it is no longer used.
@@ -785,7 +1762,7 @@ Released 2019-06-14.
* Bindings for `Array#flat` and `Array#flatMap` have been added.
[#1573](https://github.com/rustwasm/wasm-bindgen/pull/1573)
-* All `#[wasm_bindgen]` types now `AsRef` to themslves.
+* All `#[wasm_bindgen]` types now `AsRef` to themselves.
[#1583](https://github.com/rustwasm/wasm-bindgen/pull/1583)
* When using `--target web` the path passed to `init` is no longer required.
@@ -852,7 +1829,7 @@ Released 2019-05-16.
* A utility for counting the size of the `anyref` heap has been added.
[#1521](https://github.com/rustwasm/wasm-bindgen/pull/1521)
-* Passing ASCII-only strings to WASM should now be significantly faster.
+* Passing ASCII-only strings to Wasm should now be significantly faster.
[#1470](https://github.com/rustwasm/wasm-bindgen/pull/1470)
* The `selectionStart` and `selectionEnd` APIs of text areas have been enabled.
@@ -992,7 +1969,7 @@ Released 2019-04-10.
[#1416](https://github.com/rustwasm/wasm-bindgen/pull/1416)
* A `wasm_bindgen::function_table()` function has been added to expose the
- `WebAssembly.Table` and get access to it in wasm code.
+ `WebAssembly.Table` and get access to it in Wasm code.
[#1431](https://github.com/rustwasm/wasm-bindgen/pull/1431)
### Fixed
@@ -1109,7 +2086,7 @@ Released 2019-03-04.
landed and is enabled with `WASM_BINDGEN_ANYREF=1`.
[#1002](https://github.com/rustwasm/wasm-bindgen/pull/1002)
-* Support fot the new browser `TextEncode#encodeInto` API has been added.
+* Support for the new browser `TextEncode#encodeInto` API has been added.
[#1279](https://github.com/rustwasm/wasm-bindgen/pull/1279)
* JS doc comments are now added to TypeScript bindings in addition to the JS
@@ -1155,7 +2132,7 @@ Released 2019-02-15.
[#1225](https://github.com/rustwasm/wasm-bindgen/pull/1225).
* A `--remove-producers-section` flag has been added to the CLI tool to, well,
- remove the `producers` section from the final wasm file.
+ remove the `producers` section from the final Wasm file.
[#1256](https://github.com/rustwasm/wasm-bindgen/pull/1256).
### Fixed
@@ -1193,7 +2170,7 @@ Released 2019-02-12.
### Changed
* `wasm-bindgen` now internally uses the `walrus` crate to perform its
- transformations of the wasm that rustc/LLVM emits. See
+ transformations of the Wasm that rustc/LLVM emits. See
[#1237](https://github.com/rustwasm/wasm-bindgen/pull/1237).
### Fixed
@@ -1345,17 +2322,17 @@ Released 2018-12-04.
### Added
* Add a `#[wasm_bindgen(start)]` attribute to customize the `start` section of
- the wasm module.
+ the Wasm module.
[#1057](https://github.com/rustwasm/wasm-bindgen/pull/1057)
-* Add support for producing the new "producers" section of wasm binaries
+* Add support for producing the new "producers" section of Wasm binaries
[#1041](https://github.com/rustwasm/wasm-bindgen/pull/1041)
* Add support a `typescript_custom_section` attribute for producing custom
typescript abstractions
[#1048](https://github.com/rustwasm/wasm-bindgen/pull/1048)
-* Generate `*.d.ts` files for wasm files in addition to the JS bindings
+* Generate `*.d.ts` files for Wasm files in addition to the JS bindings
[#1053](https://github.com/rustwasm/wasm-bindgen/pull/1053)
* Add a feature to assert that all attributes in `#[wasm_bindgen]` are used to
@@ -1632,7 +2609,7 @@ Released 2018-09-07
### Fixed
-* The "names" section of the wasm binary is now correctly preserved by
+* The "names" section of the Wasm binary is now correctly preserved by
wasm-bindgen.
--------------------------------------------------------------------------------
diff --git a/Cargo.toml b/Cargo.toml
index d6523f44f1f..cb97505b650 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,17 +1,19 @@
[package]
-name = "wasm-bindgen"
-version = "0.2.80"
authors = ["The wasm-bindgen Developers"]
-license = "MIT/Apache-2.0"
-readme = "README.md"
categories = ["wasm"]
-repository = "https://github.com/rustwasm/wasm-bindgen"
-homepage = "https://rustwasm.github.io/"
-documentation = "https://docs.rs/wasm-bindgen"
description = """
Easy support for interacting between JS and Rust.
"""
-edition = "2018"
+documentation = "https://docs.rs/wasm-bindgen"
+edition = "2021"
+homepage = "https://rustwasm.github.io/"
+include = ["/build.rs", "/LICENSE-*", "/src"]
+license = "MIT OR Apache-2.0"
+name = "wasm-bindgen"
+readme = "README.md"
+repository = "https://github.com/rustwasm/wasm-bindgen"
+rust-version = "1.57"
+version = "0.2.100"
[package.metadata.docs.rs]
features = ["serde-serialize"]
@@ -20,42 +22,77 @@ features = ["serde-serialize"]
test = false
[features]
-default = ["spans", "std"]
-spans = ["wasm-bindgen-macro/spans"]
-std = []
-serde-serialize = ["serde", "serde_json", "std"]
-nightly = []
+default = ["std", "msrv"]
enable-interning = ["std"]
+serde-serialize = ["serde", "serde_json", "std"]
+spans = []
+std = []
+
+# Opt-in for Rust language features that require a higher MSRV.
+#
+# The current rustc version is detected at compile-time, so enabling this
+# feature for older compilers will NOT result in a compilation error. Instead,
+# any unsupported language feature will not be used.
+msrv = ["rustversion"]
# Whether or not the `#[wasm_bindgen]` macro is strict and generates an error on
# all unused attributes
strict-macro = ["wasm-bindgen-macro/strict-macro"]
-# This is only for debugging wasm-bindgen! No stability guarantees, so enable
+# INTERNAL ONLY: Enables gg-alloc as system allocator when using wasm-bindgen-test to check that large pointers
+# are handled correctly
+gg-alloc = ["wasm-bindgen-test/gg-alloc"]
+
+# INTERNAL ONLY: This is only for debugging wasm-bindgen! No stability guarantees, so enable
# this at your own peril!
xxx_debug_only_print_generated_code = ["wasm-bindgen-macro/xxx_debug_only_print_generated_code"]
[dependencies]
-wasm-bindgen-macro = { path = "crates/macro", version = "=0.2.80" }
+cfg-if = "1.0.0"
+once_cell = { version = "1.12", default-features = false }
+rustversion = { version = "1.0", optional = true }
serde = { version = "1.0", optional = true }
serde_json = { version = "1.0", optional = true }
-cfg-if = "1.0.0"
+wasm-bindgen-macro = { path = "crates/macro", version = "=0.2.100" }
+
+[dev-dependencies]
+once_cell = "1"
+wasm-bindgen-test = { path = 'crates/test' }
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
-js-sys = { path = 'crates/js-sys', version = '0.3.57' }
-wasm-bindgen-test = { path = 'crates/test', version = '=0.3.30' }
-wasm-bindgen-futures = { path = 'crates/futures', version = '=0.4.30' }
+js-sys = { path = 'crates/js-sys' }
+paste = "1"
serde_derive = "1.0"
-wasm-bindgen-test-crate-a = { path = 'tests/crates/a', version = '0.1' }
-wasm-bindgen-test-crate-b = { path = 'tests/crates/b', version = '0.1' }
+wasm-bindgen-futures = { path = 'crates/futures' }
+wasm-bindgen-test-crate-a = { path = 'tests/crates/a' }
+wasm-bindgen-test-crate-b = { path = 'tests/crates/b' }
+
+[lints.rust]
+unexpected_cfgs = { level = "warn", check-cfg = ['cfg(wasm_bindgen_unstable_test_coverage)'] }
+
+[lints.clippy]
+large_enum_variant = "allow"
+new_without_default = "allow"
+overly_complex_bool_expr = "allow"
+too_many_arguments = "allow"
+type_complexity = "allow"
+
+[workspace.lints.clippy]
+large_enum_variant = "allow"
+new_without_default = "allow"
+overly_complex_bool_expr = "allow"
+too_many_arguments = "allow"
+type_complexity = "allow"
[workspace]
+exclude = ["crates/msrv/resolver", "crates/msrv/lib", "crates/msrv/cli"]
members = [
"benchmarks",
"crates/cli",
"crates/js-sys",
"crates/test",
"crates/test/sample",
+ "crates/example-tests",
"crates/typescript-tests",
"crates/web-sys",
"crates/webidl",
@@ -68,6 +105,7 @@ members = [
"examples/deno",
"examples/dom",
"examples/duck-typed-interfaces",
+ "examples/explicit-resource-management",
"examples/fetch",
"examples/guide-supported-types-examples",
"examples/hello_world",
@@ -78,10 +116,10 @@ members = [
"examples/raytrace-parallel",
"examples/request-animation-frame",
"examples/todomvc",
+ "examples/wasm-audio-worklet",
"examples/wasm-in-wasm",
"examples/wasm-in-wasm-imports",
"examples/wasm-in-web-worker",
- "examples/wasm2js",
"examples/weather_report",
"examples/webaudio",
"examples/webgl",
@@ -90,11 +128,13 @@ members = [
"examples/webxr",
"examples/without-a-bundler",
"examples/without-a-bundler-no-modules",
+ "examples/synchronous-instantiation",
"tests/no-std",
]
+resolver = "2"
[patch.crates-io]
+js-sys = { path = 'crates/js-sys' }
wasm-bindgen = { path = '.' }
wasm-bindgen-futures = { path = 'crates/futures' }
-js-sys = { path = 'crates/js-sys' }
web-sys = { path = 'crates/web-sys' }
diff --git a/README.md b/README.md
index cf5dafcfd7e..d8e07bc0952 100644
--- a/README.md
+++ b/README.md
@@ -7,14 +7,14 @@
-
+
- Guide
+ Guide (main branch) | API Docs |
@@ -26,6 +26,24 @@
Built with 🦀🕸 by The Rust and WebAssembly Working Group
+## Install `wasm-bindgen-cli`
+
+You can install it using `cargo install`:
+
+```
+cargo install wasm-bindgen-cli
+```
+
+Or, you can download it from the
+[release page](https://github.com/rustwasm/wasm-bindgen/releases).
+
+If you have [`cargo-binstall`](https://crates.io/crates/cargo-binstall) installed,
+then you can install the pre-built artifacts by running:
+
+```
+cargo binstall wasm-bindgen-cli
+```
+
## Example
Import JavaScript things into Rust and export Rust things to JavaScript.
@@ -69,7 +87,7 @@ greet("World!");
* **Designed with the ["Web IDL bindings" proposal][webidl-bindings] in mind.**
Eventually, there won't be any JavaScript shims between Rust-generated wasm
- functions and native DOM methods. Because the wasm functions are statically
+ functions and native DOM methods. Because the Wasm functions are statically
type checked, some of those native methods' dynamic type checks should become
unnecessary, promising to unlock even-faster-than-JavaScript DOM access.
@@ -90,6 +108,12 @@ You can find general documentation about using Rust and WebAssembly together
- [web-sys](https://docs.rs/web-sys)
- [wasm-bindgen-futures](https://docs.rs/wasm-bindgen-futures)
+## MSRV Policy
+
+Libraries that are released on [crates.io](https://crates.io) have a MSRV of v1.57. Changes to the MSRV will be accompanied by a minor version bump.
+
+CLI tools and their corresponding support libraries have a MSRV of v1.76. Changes to the MSRV will be accompanied by a patch version bump.
+
## License
This project is licensed under either of
@@ -110,4 +134,4 @@ Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in this project by you, as defined in the Apache-2.0 license,
shall be dual licensed as above, without any additional terms or conditions.
-[contributing]: https://rustwasm.github.io/docs/wasm-bindgen/contributing/index.html
\ No newline at end of file
+[contributing]: https://rustwasm.github.io/docs/wasm-bindgen/contributing/index.html
diff --git a/_package.json b/_package.json
deleted file mode 100644
index 95632f89360..00000000000
--- a/_package.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "scripts": {
- "build": "webpack",
- "serve": "webpack serve"
- },
- "devDependencies": {
- "@wasm-tool/wasm-pack-plugin": "1.5.0",
- "html-webpack-plugin": "^5.3.2",
- "text-encoding": "^0.7.0",
- "webpack": "^5.49.0",
- "webpack-cli": "^4.7.2",
- "webpack-dev-server": "^3.11.2"
- }
-}
diff --git a/benchmarks/Cargo.toml b/benchmarks/Cargo.toml
index 7ffef776019..8c41d2d7998 100644
--- a/benchmarks/Cargo.toml
+++ b/benchmarks/Cargo.toml
@@ -1,11 +1,14 @@
[package]
-name = "wasm-bindgen-benchmark"
-version = "0.1.0"
authors = ["The wasm-bindgen Developers"]
+edition = "2021"
+name = "wasm-bindgen-benchmark"
+publish = false
+version = "0.0.0"
[dependencies]
-wasm-bindgen = "0.2.43"
-web-sys = { version = "0.3.20", features = ['Node'] }
+js-sys = { path = '../crates/js-sys' }
+wasm-bindgen = { path = '../' }
+web-sys = { path = '../crates/web-sys', features = ['Node'] }
[lib]
crate-type = ['cdylib']
diff --git a/benchmarks/README.md b/benchmarks/README.md
index 2309bcb8af2..ca1619cd3cd 100644
--- a/benchmarks/README.md
+++ b/benchmarks/README.md
@@ -11,17 +11,10 @@ performance suite for WebAssembly for Rust.
## Building and Running
-First, copy the benchmarks to a temporary directory:
-
-```
-$ cp ./benchmarks /some/other/directory
-```
-
-Next, `cd` into that directory and execute:
-
```
+$ cd benchmarks
$ cargo build --release --target wasm32-unknown-unknown
-$ wasm-bindgen --target web ./target/wasm32-unknown-unknown/release/crate.wasm
+$ cargo run --package wasm-bindgen-cli -- --out-dir pkg --target web ../target/wasm32-unknown-unknown/release/wasm_bindgen_benchmark.wasm
```
Next, use your favorite static file server to host the current directory. For
diff --git a/benchmarks/globals.js b/benchmarks/globals.js
index 0412158e34a..13c8fe14b4a 100644
--- a/benchmarks/globals.js
+++ b/benchmarks/globals.js
@@ -1,5 +1,16 @@
export function jsthunk() {}
export function add(a, b) { return a + b; }
+export function use_baz(baz) {
+ if (baz !== Baz['variant-2']) {
+ throw new Error("Passed wrong variant");
+ }
+ }
export class Foo {
bar() {}
}
+
+export const Baz = {
+ 'variant-1': 'variant-1',
+ 'variant-2': 'variant-2',
+ 'variant-3': 'variant-3',
+}
diff --git a/benchmarks/index.html b/benchmarks/index.html
index 42cd3dc515d..b20d27e5d45 100644
--- a/benchmarks/index.html
+++ b/benchmarks/index.html
@@ -22,7 +22,7 @@
- Source code
+ Source code
JS / wasm-bindgen comparison
@@ -56,7 +56,7 @@
JS / wasm-bindgen comparison
This benchmarks tests out how long it take JS to call a thunk in
the given language. For example JS will call a JS thunk or
- JS will call a wasm function that does nothing.
+ JS will call a Wasm function that does nothing.
@@ -131,7 +131,7 @@
JS / wasm-bindgen comparison
This benchmarks calculates the 40th fibonacci number. It in
- theory should favor wasm since wasm is "better a compute", but
+ theory should favor Wasm since Wasm is "better a compute", but
a good JIT will probably make the code roughly equivalent.
@@ -275,6 +275,20 @@
wasm-bindgen benchmarks
+
+
+ Call a custom JS function with a string enum value parameter
+
+ (?)
+
+
+ Benchmarks the speed of passing string enums to javascript
+
- This benchmarks the overhead of passing strings to wasm and
+ This benchmarks the overhead of passing strings to Wasm and
also receiving them from wasm.
diff --git a/benchmarks/index.js b/benchmarks/index.js
index ac603b63c76..a8cbae01e77 100644
--- a/benchmarks/index.js
+++ b/benchmarks/index.js
@@ -16,6 +16,7 @@ import wbindgen_init, {
call_first_child_final_n_times as wbindgen_call_first_child_final_n_times,
call_first_child_structural_n_times as wbindgen_call_first_child_structural_n_times,
call_foo_bar_final_n_times as wbindgen_call_foo_bar_final_n_times,
+ call_use_baz_n_times as wbindgen_call_use_baz_n_times,
call_foo_bar_structural_n_times as wbindgen_call_foo_bar_structural_n_times,
str_roundtrip as wbindgen_str_roundtrip,
} from './pkg/wasm_bindgen_benchmark.js';
@@ -80,6 +81,7 @@ function makeBenchmarks() {
const foo = new globals.Foo();
benchmarks.wbindgen_call_foo_bar_final_n_times = () => wbindgen_call_foo_bar_final_n_times(10000, foo);
benchmarks.wbindgen_call_foo_bar_structural_n_times = () => wbindgen_call_foo_bar_structural_n_times(10000, foo);
+ benchmarks.wbindgen_call_use_baz_n_times = () => wbindgen_call_use_baz_n_times(10000);
const strings = {
@@ -158,7 +160,7 @@ function executeBenchmark(name, bm) {
});
}
-// Load wasm files and when they're done (plus the DOM) then we initialize
+// Load Wasm files and when they're done (plus the DOM) then we initialize
// everything
const wasms = [];
wasms.push(wbindgen_init('./pkg/wasm_bindgen_benchmark_bg.wasm'));
diff --git a/benchmarks/src/lib.rs b/benchmarks/src/lib.rs
index f6c375db6ad..c5abf1c560c 100644
--- a/benchmarks/src/lib.rs
+++ b/benchmarks/src/lib.rs
@@ -2,7 +2,6 @@ extern crate wasm_bindgen;
extern crate web_sys;
use wasm_bindgen::prelude::*;
-use wasm_bindgen::JsCast;
use web_sys::Node;
#[wasm_bindgen(raw_module = "../globals.js")]
@@ -12,6 +11,9 @@ extern "C" {
#[wasm_bindgen(js_name = add)]
fn js_add(a: i32, b: i32) -> i32;
+ #[wasm_bindgen(js_name = use_baz)]
+ fn js_use_baz(val: Baz);
+
pub type Foo;
#[wasm_bindgen(method, final, js_name = bar)]
fn bar_final(this: &Foo);
@@ -24,6 +26,14 @@ extern "C" {
fn doesnt_throw_catch() -> Result<(), JsValue>;
}
+#[wasm_bindgen]
+#[derive(Copy, Clone)]
+pub enum Baz {
+ Variant1 = "variant-1",
+ Variant2 = "variant-2",
+ Variant3 = "variant-3",
+}
+
#[wasm_bindgen]
pub fn call_js_thunk_n_times(n: usize) {
for _ in 0..n {
@@ -60,7 +70,7 @@ pub fn fibonacci(n: i32) -> i32 {
unsafe {
FIB_HIGH = (a >> 32) as i32;
}
- return a as i32;
+ a as i32
}
#[wasm_bindgen]
@@ -69,16 +79,23 @@ pub fn fibonacci_high() -> i32 {
}
#[wasm_bindgen]
-pub fn call_foo_bar_final_n_times(n: usize, foo: &Foo) {
+pub fn call_foo_bar_final_n_times(n: usize, js_foo: &Foo) {
+ for _ in 0..n {
+ js_foo.bar_final();
+ }
+}
+
+#[wasm_bindgen]
+pub fn call_foo_bar_structural_n_times(n: usize, js_foo: &Foo) {
for _ in 0..n {
- foo.bar_final();
+ js_foo.bar_structural();
}
}
#[wasm_bindgen]
-pub fn call_foo_bar_structural_n_times(n: usize, foo: &Foo) {
+pub fn call_use_baz_n_times(n: usize) {
for _ in 0..n {
- foo.bar_structural();
+ js_use_baz(Baz::Variant2);
}
}
diff --git a/crates/backend/Cargo.toml b/crates/backend/Cargo.toml
index d4b10932f52..cc7566ffa1d 100644
--- a/crates/backend/Cargo.toml
+++ b/crates/backend/Cargo.toml
@@ -1,25 +1,28 @@
[package]
-name = "wasm-bindgen-backend"
-version = "0.2.80"
authors = ["The wasm-bindgen Developers"]
-license = "MIT/Apache-2.0"
-repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/backend"
-homepage = "https://rustwasm.github.io/wasm-bindgen/"
-documentation = "https://docs.rs/wasm-bindgen-backend"
description = """
Backend code generation of the wasm-bindgen tool
"""
-edition = "2018"
+documentation = "https://docs.rs/wasm-bindgen-backend"
+edition = "2021"
+homepage = "https://rustwasm.github.io/wasm-bindgen/"
+include = ["/LICENSE-*", "/src"]
+license = "MIT OR Apache-2.0"
+name = "wasm-bindgen-backend"
+repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/backend"
+rust-version = "1.57"
+version = "0.2.100"
[features]
-spans = []
extra-traits = ["syn/extra-traits"]
[dependencies]
bumpalo = "3.0.0"
-lazy_static = "1.0.2"
log = "0.4"
proc-macro2 = "1.0"
quote = '1.0'
-syn = { version = '1.0', features = ['full'] }
-wasm-bindgen-shared = { path = "../shared", version = "=0.2.80" }
+syn = { version = '2.0', features = ['full'] }
+wasm-bindgen-shared = { path = "../shared", version = "=0.2.100" }
+
+[lints]
+workspace = true
diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs
index 80af028f422..0fed5f18c54 100644
--- a/crates/backend/src/ast.rs
+++ b/crates/backend/src/ast.rs
@@ -1,30 +1,55 @@
//! A representation of the Abstract Syntax Tree of a Rust program,
-//! with all the added metadata necessary to generate WASM bindings
+//! with all the added metadata necessary to generate Wasm bindings
//! for it.
-use crate::Diagnostic;
+use crate::{util::ShortHash, Diagnostic};
use proc_macro2::{Ident, Span};
use std::hash::{Hash, Hasher};
-use syn;
+use syn::Path;
use wasm_bindgen_shared as shared;
/// An abstract syntax tree representing a rust program. Contains
/// extra information for joining up this rust code with javascript.
#[cfg_attr(feature = "extra-traits", derive(Debug))]
-#[derive(Default, Clone)]
+#[derive(Clone)]
pub struct Program {
/// rust -> js interfaces
pub exports: Vec,
/// js -> rust interfaces
pub imports: Vec,
+ /// linked-to modules
+ pub linked_modules: Vec,
/// rust enums
pub enums: Vec,
/// rust structs
pub structs: Vec,
/// custom typescript sections to be included in the definition file
- pub typescript_custom_sections: Vec,
+ pub typescript_custom_sections: Vec,
/// Inline JS snippets
pub inline_js: Vec,
+ /// Path to wasm_bindgen
+ pub wasm_bindgen: Path,
+ /// Path to js_sys
+ pub js_sys: Path,
+ /// Path to wasm_bindgen_futures
+ pub wasm_bindgen_futures: Path,
+}
+
+impl Default for Program {
+ fn default() -> Self {
+ Self {
+ exports: Default::default(),
+ imports: Default::default(),
+ linked_modules: Default::default(),
+ enums: Default::default(),
+ structs: Default::default(),
+ typescript_custom_sections: Default::default(),
+ inline_js: Default::default(),
+ wasm_bindgen: syn::parse_quote! { wasm_bindgen },
+ js_sys: syn::parse_quote! { js_sys },
+ wasm_bindgen_futures: syn::parse_quote! { wasm_bindgen_futures },
+ }
+ }
}
impl Program {
@@ -37,8 +62,25 @@ impl Program {
&& self.typescript_custom_sections.is_empty()
&& self.inline_js.is_empty()
}
+
+ /// Name of the link function for a specific linked module
+ pub fn link_function_name(&self, idx: usize) -> String {
+ let hash = match &self.linked_modules[idx] {
+ ImportModule::Inline(idx, _) => ShortHash((1, &self.inline_js[*idx])).to_string(),
+ other => ShortHash((0, other)).to_string(),
+ };
+ format!("__wbindgen_link_{}", hash)
+ }
}
+/// An abstract syntax tree representing a link to a module in Rust.
+/// In contrast to Program, LinkToModule must expand to an expression.
+/// linked_modules of the inner Program must contain exactly one element
+/// whose link is produced by the expression.
+#[cfg_attr(feature = "extra-traits", derive(Debug))]
+#[derive(Clone)]
+pub struct LinkToModule(pub Program);
+
/// A rust to js interface. Allows interaction with rust objects/functions
/// from javascript.
#[cfg_attr(feature = "extra-traits", derive(Debug))]
@@ -58,14 +100,18 @@ pub struct Export {
pub rust_class: Option,
/// The name of the rust function/method on the rust side.
pub rust_name: Ident,
- /// Whether or not this function should be flagged as the wasm start
+ /// Whether or not this function should be flagged as the Wasm start
/// function.
pub start: bool,
+ /// Path to wasm_bindgen
+ pub wasm_bindgen: Path,
+ /// Path to wasm_bindgen_futures
+ pub wasm_bindgen_futures: Path,
}
/// The 3 types variations of `self`.
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
-#[derive(Clone)]
+#[derive(Copy, Clone)]
pub enum MethodSelf {
/// `self`
ByValue,
@@ -79,8 +125,8 @@ pub enum MethodSelf {
#[cfg_attr(feature = "extra-traits", derive(Debug))]
#[derive(Clone)]
pub struct Import {
- /// The type of module being imported from
- pub module: ImportModule,
+ /// The type of module being imported from, if any
+ pub module: Option,
/// The namespace to access the item through, if any
pub js_namespace: Option>,
/// The type of item being imported
@@ -91,8 +137,6 @@ pub struct Import {
#[cfg_attr(feature = "extra-traits", derive(Debug))]
#[derive(Clone)]
pub enum ImportModule {
- /// No module / import from global scope
- None,
/// Import from the named module, with relative paths interpreted
Named(String, Span),
/// Import from the named module, without interpreting paths
@@ -104,21 +148,9 @@ pub enum ImportModule {
impl Hash for ImportModule {
fn hash(&self, h: &mut H) {
match self {
- ImportModule::None => {
- 0u8.hash(h);
- }
- ImportModule::Named(name, _) => {
- 1u8.hash(h);
- name.hash(h);
- }
- ImportModule::Inline(idx, _) => {
- 2u8.hash(h);
- idx.hash(h);
- }
- ImportModule::RawNamed(name, _) => {
- 3u8.hash(h);
- name.hash(h);
- }
+ ImportModule::Named(name, _) => (1u8, name).hash(h),
+ ImportModule::Inline(idx, _) => (2u8, idx).hash(h),
+ ImportModule::RawNamed(name, _) => (3u8, name).hash(h),
}
}
}
@@ -131,10 +163,12 @@ pub enum ImportKind {
Function(ImportFunction),
/// Importing a static value
Static(ImportStatic),
+ /// Importing a static string
+ String(ImportString),
/// Importing a type/class
Type(ImportType),
/// Importing a JS enum
- Enum(ImportEnum),
+ Enum(StringEnum),
}
/// A function being imported from JS
@@ -163,7 +197,11 @@ pub struct ImportFunction {
/// necessary conversions (EG adding a try/catch to change a thrown error into a Result)
pub shim: Ident,
/// The doc comment on this import, if one is provided
- pub doc_comment: Option,
+ pub doc_comment: String,
+ /// Path to wasm_bindgen
+ pub wasm_bindgen: Path,
+ /// Path to wasm_bindgen_futures
+ pub wasm_bindgen_futures: Path,
}
/// The type of a function being imported
@@ -209,10 +247,10 @@ pub struct Operation {
pub enum OperationKind {
/// A standard method, nothing special
Regular,
- /// A method for getting the value of the provided Ident
- Getter(Option),
- /// A method for setting the value of the provided Ident
- Setter(Option),
+ /// A method for getting the value of the provided Ident or String
+ Getter(Option),
+ /// A method for setting the value of the provided Ident or String
+ Setter(Option),
/// A dynamically intercepted getter
IndexingGetter,
/// A dynamically intercepted setter
@@ -235,6 +273,41 @@ pub struct ImportStatic {
pub rust_name: Ident,
/// The name of this static on the JS side
pub js_name: String,
+ /// Path to wasm_bindgen
+ pub wasm_bindgen: Path,
+ /// Version of `thread_local`, if any.
+ pub thread_local: Option,
+}
+
+/// Which version of the `thread_local` attribute is enabled.
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum ThreadLocal {
+ /// V1.
+ V1,
+ /// V2.
+ V2,
+}
+
+/// The type of a static string being imported
+#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
+#[derive(Clone)]
+pub struct ImportString {
+ /// The visibility of this static string in Rust
+ pub vis: syn::Visibility,
+ /// The type specified by the user, which we only use to show an error if the wrong type is used.
+ pub ty: syn::Type,
+ /// The name of the shim function used to access this static
+ pub shim: Ident,
+ /// The name of this static on the Rust side
+ pub rust_name: Ident,
+ /// Path to wasm_bindgen
+ pub wasm_bindgen: Path,
+ /// Path to js_sys
+ pub js_sys: Path,
+ /// The string to export.
+ pub string: String,
+ /// Version of `thread_local`.
+ pub thread_local: ThreadLocal,
}
/// The metadata for a type being imported
@@ -263,22 +336,32 @@ pub struct ImportType {
pub vendor_prefixes: Vec,
/// If present, don't generate a `Deref` impl
pub no_deref: bool,
+ /// Path to wasm_bindgen
+ pub wasm_bindgen: Path,
}
-/// The metadata for an Enum being imported
+/// The metadata for a String Enum
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
#[derive(Clone)]
-pub struct ImportEnum {
+pub struct StringEnum {
/// The Rust enum's visibility
pub vis: syn::Visibility,
/// The Rust enum's identifiers
pub name: Ident,
+ /// The name of this string enum in JS/TS code
+ pub js_name: String,
/// The Rust identifiers for the variants
pub variants: Vec,
/// The JS string values of the variants
pub variant_values: Vec,
+ /// The doc comments on this enum, if any
+ pub comments: Vec,
/// Attributes to apply to the Rust enum
pub rust_attrs: Vec,
+ /// Whether to generate a typescript definition for this enum
+ pub generate_typescript: bool,
+ /// Path to wasm_bindgen
+ pub wasm_bindgen: Path,
}
/// Information about a function being imported or exported
@@ -292,21 +375,53 @@ pub struct Function {
/// Whether the function has a js_name attribute
pub renamed_via_js_name: bool,
/// The arguments to the function
- pub arguments: Vec,
- /// The return type of the function, if provided
- pub ret: Option,
+ pub arguments: Vec,
+ /// The data of return type of the function
+ pub ret: Option,
/// Any custom attributes being applied to the function
pub rust_attrs: Vec,
/// The visibility of this function in Rust
pub rust_vis: syn::Visibility,
+ /// Whether this is an `unsafe` function
+ pub r#unsafe: bool,
/// Whether this is an `async` function
pub r#async: bool,
/// Whether to generate a typescript definition for this function
pub generate_typescript: bool,
+ /// Whether to generate jsdoc documentation for this function
+ pub generate_jsdoc: bool,
+ /// Whether this is a function with a variadict parameter
+ pub variadic: bool,
+}
+
+/// Information about a function's return
+#[cfg_attr(feature = "extra-traits", derive(Debug))]
+#[derive(Clone)]
+pub struct FunctionReturnData {
+ /// Specifies the type of the function's return
+ pub r#type: syn::Type,
+ /// Specifies the JS return type override
+ pub js_type: Option,
+ /// Specifies the return description
+ pub desc: Option,
+}
+
+/// Information about a function's argument
+#[cfg_attr(feature = "extra-traits", derive(Debug))]
+#[derive(Clone)]
+pub struct FunctionArgumentData {
+ /// Specifies the type of the function's argument
+ pub pat_type: syn::PatType,
+ /// Specifies the JS argument name override
+ pub js_name: Option,
+ /// Specifies the JS function argument type override
+ pub js_type: Option,
+ /// Specifies the argument description
+ pub desc: Option,
}
/// Information about a Struct being exported
-#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
+#[cfg_attr(feature = "extra-traits", derive(Debug))]
#[derive(Clone)]
pub struct Struct {
/// The name of the struct in Rust code
@@ -321,10 +436,12 @@ pub struct Struct {
pub is_inspectable: bool,
/// Whether to generate a typescript definition for this struct
pub generate_typescript: bool,
+ /// Path to wasm_bindgen
+ pub wasm_bindgen: Path,
}
/// The field of a struct
-#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
+#[cfg_attr(feature = "extra-traits", derive(Debug))]
#[derive(Clone)]
pub struct StructField {
/// The name of the field in Rust code
@@ -345,11 +462,19 @@ pub struct StructField {
pub comments: Vec,
/// Whether to generate a typescript definition for this field
pub generate_typescript: bool,
- /// Whether to use .clone() in the auto-generated getter for this field
- pub getter_with_clone: bool,
+ /// Whether to generate jsdoc documentation for this field
+ pub generate_jsdoc: bool,
+ /// The span of the `#[wasm_bindgen(getter_with_clone)]` attribute applied
+ /// to this field, if any.
+ ///
+ /// If this is `Some`, the auto-generated getter for this field must clone
+ /// the field instead of copying it.
+ pub getter_with_clone: Option,
+ /// Path to wasm_bindgen
+ pub wasm_bindgen: Path,
}
-/// Information about an Enum being exported
+/// The metadata for an Enum
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
#[derive(Clone)]
pub struct Enum {
@@ -357,6 +482,9 @@ pub struct Enum {
pub rust_name: Ident,
/// The name of this enum in JS code
pub js_name: String,
+ /// Whether the variant values and hole are signed, meaning that they
+ /// represent the bits of a `i32` value.
+ pub signed: bool,
/// The variants provided by this enum
pub variants: Vec,
/// The doc comments on this enum, if any
@@ -365,6 +493,8 @@ pub struct Enum {
pub hole: u32,
/// Whether to generate a typescript definition for this enum
pub generate_typescript: bool,
+ /// Path to wasm_bindgen
+ pub wasm_bindgen: Path,
}
/// The variant of an enum
@@ -403,16 +533,26 @@ pub enum TypeLocation {
ExportRet,
}
+/// An enum representing either a literal value (`Lit`) or an expression (`syn::Expr`).
+#[cfg_attr(feature = "extra-traits", derive(Debug))]
+#[derive(Clone)]
+pub enum LitOrExpr {
+ /// Represents an expression that needs to be evaluated before it can be encoded
+ Expr(syn::Expr),
+ /// Represents a literal string that can be directly encoded.
+ Lit(String),
+}
+
impl Export {
/// Mangles a rust -> javascript export, so that the created Ident will be unique over function
/// name and class name, if the function belongs to a javascript class.
pub(crate) fn rust_symbol(&self) -> Ident {
let mut generated_name = String::from("__wasm_bindgen_generated");
if let Some(class) = &self.js_class {
- generated_name.push_str("_");
+ generated_name.push('_');
generated_name.push_str(class);
}
- generated_name.push_str("_");
+ generated_name.push('_');
generated_name.push_str(&self.function.name.to_string());
Ident::new(&generated_name, Span::call_site())
}
@@ -435,6 +575,7 @@ impl ImportKind {
match *self {
ImportKind::Function(_) => true,
ImportKind::Static(_) => false,
+ ImportKind::String(_) => false,
ImportKind::Type(_) => false,
ImportKind::Enum(_) => false,
}
diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs
index ac179518036..01450ccb9df 100644
--- a/crates/backend/src/codegen.rs
+++ b/crates/backend/src/codegen.rs
@@ -1,13 +1,15 @@
use crate::ast;
use crate::encode;
-use crate::util::ShortHash;
+use crate::encode::EncodeChunk;
use crate::Diagnostic;
-use proc_macro2::{Ident, Literal, Span, TokenStream};
+use proc_macro2::{Ident, Span, TokenStream};
+use quote::format_ident;
+use quote::quote_spanned;
use quote::{quote, ToTokens};
+use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
-use std::sync::atomic::{AtomicUsize, Ordering};
-use std::sync::Mutex;
-use syn;
+use syn::parse_quote;
+use syn::spanned::Spanned;
use wasm_bindgen_shared as shared;
/// A trait for converting AST structs into Tokens and adding them to a TokenStream,
@@ -43,7 +45,11 @@ impl TryToTokens for ast::Program {
}
}
for i in self.imports.iter() {
- DescribeImport { kind: &i.kind }.to_tokens(tokens);
+ DescribeImport {
+ kind: &i.kind,
+ wasm_bindgen: &self.wasm_bindgen,
+ }
+ .to_tokens(tokens);
// If there is a js namespace, check that name isn't a type. If it is,
// this import might be a method on that type.
@@ -79,17 +85,9 @@ impl TryToTokens for ast::Program {
Diagnostic::from_vec(errors)?;
// Generate a static which will eventually be what lives in a custom section
- // of the wasm executable. For now it's just a plain old static, but we'll
+ // of the Wasm executable. For now it's just a plain old static, but we'll
// eventually have it actually in its own section.
- static CNT: AtomicUsize = AtomicUsize::new(0);
-
- let generated_static_name = format!(
- "__WASM_BINDGEN_GENERATED_{}",
- ShortHash(CNT.fetch_add(1, Ordering::SeqCst)),
- );
- let generated_static_name = Ident::new(&generated_static_name, Span::call_site());
-
// See comments in `crates/cli-support/src/lib.rs` about what this
// `schema_version` is.
let prefix_json = format!(
@@ -97,17 +95,54 @@ impl TryToTokens for ast::Program {
shared::SCHEMA_VERSION,
shared::version()
);
+
+ let wasm_bindgen = &self.wasm_bindgen;
+
let encoded = encode::encode(self)?;
- let mut bytes = Vec::new();
- bytes.push((prefix_json.len() >> 0) as u8);
- bytes.push((prefix_json.len() >> 8) as u8);
- bytes.push((prefix_json.len() >> 16) as u8);
- bytes.push((prefix_json.len() >> 24) as u8);
- bytes.extend_from_slice(prefix_json.as_bytes());
- bytes.extend_from_slice(&encoded.custom_section);
- let generated_static_length = bytes.len();
- let generated_static_value = syn::LitByteStr::new(&bytes, Span::call_site());
+ let encoded_chunks: Vec<_> = encoded
+ .custom_section
+ .iter()
+ .map(|chunk| match chunk {
+ EncodeChunk::EncodedBuf(buf) => {
+ let buf = syn::LitByteStr::new(buf.as_slice(), Span::call_site());
+ quote!(#buf)
+ }
+ EncodeChunk::StrExpr(expr) => {
+ // encode expr as str
+ quote!({
+ use #wasm_bindgen::__rt::{encode_u32_to_fixed_len_bytes};
+ const _STR_EXPR: &str = #expr;
+ const _STR_EXPR_BYTES: &[u8] = _STR_EXPR.as_bytes();
+ const _STR_EXPR_BYTES_LEN: usize = _STR_EXPR_BYTES.len() + 5;
+ const _ENCODED_BYTES: [u8; _STR_EXPR_BYTES_LEN] = flat_byte_slices([
+ &encode_u32_to_fixed_len_bytes(_STR_EXPR_BYTES.len() as u32),
+ _STR_EXPR_BYTES,
+ ]);
+ &_ENCODED_BYTES
+ })
+ }
+ })
+ .collect();
+
+ let chunk_len = encoded_chunks.len();
+
+ // concatenate all encoded chunks and write the length in front of the chunk;
+ let encode_bytes = quote!({
+ const _CHUNK_SLICES: [&[u8]; #chunk_len] = [
+ #(#encoded_chunks,)*
+ ];
+ #[allow(long_running_const_eval)]
+ const _CHUNK_LEN: usize = flat_len(_CHUNK_SLICES);
+ #[allow(long_running_const_eval)]
+ const _CHUNKS: [u8; _CHUNK_LEN] = flat_byte_slices(_CHUNK_SLICES);
+
+ const _LEN_BYTES: [u8; 4] = (_CHUNK_LEN as u32).to_le_bytes();
+ const _ENCODED_BYTES_LEN: usize = _CHUNK_LEN + 4;
+ #[allow(long_running_const_eval)]
+ const _ENCODED_BYTES: [u8; _ENCODED_BYTES_LEN] = flat_byte_slices([&_LEN_BYTES, &_CHUNKS]);
+ &_ENCODED_BYTES
+ });
// We already consumed the contents of included files when generating
// the custom section, but we want to make sure that updates to the
@@ -122,17 +157,28 @@ impl TryToTokens for ast::Program {
quote! { include_str!(#file) }
});
+ let len = prefix_json.len() as u32;
+ let prefix_json_bytes = [&len.to_le_bytes()[..], prefix_json.as_bytes()].concat();
+ let prefix_json_bytes = syn::LitByteStr::new(&prefix_json_bytes, Span::call_site());
+
(quote! {
- #[cfg(target_arch = "wasm32")]
+ #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))]
#[automatically_derived]
- #[link_section = "__wasm_bindgen_unstable"]
- #[doc(hidden)]
- pub static #generated_static_name: [u8; #generated_static_length] = {
+ const _: () = {
+ use #wasm_bindgen::__rt::{flat_len, flat_byte_slices};
+
static _INCLUDED_FILES: &[&str] = &[#(#file_dependencies),*];
- *#generated_static_value
- };
+ const _ENCODED_BYTES: &[u8] = #encode_bytes;
+ const _PREFIX_JSON_BYTES: &[u8] = #prefix_json_bytes;
+ const _ENCODED_BYTES_LEN: usize = _ENCODED_BYTES.len();
+ const _PREFIX_JSON_BYTES_LEN: usize = _PREFIX_JSON_BYTES.len();
+ const _LEN: usize = _PREFIX_JSON_BYTES_LEN + _ENCODED_BYTES_LEN;
+ #[link_section = "__wasm_bindgen_unstable"]
+ #[allow(long_running_const_eval)]
+ static _GENERATED: [u8; _LEN] = flat_byte_slices([_PREFIX_JSON_BYTES, _ENCODED_BYTES]);
+ };
})
.to_tokens(tokens);
@@ -140,27 +186,55 @@ impl TryToTokens for ast::Program {
}
}
+impl TryToTokens for ast::LinkToModule {
+ fn try_to_tokens(&self, tokens: &mut TokenStream) -> Result<(), Diagnostic> {
+ let mut program = TokenStream::new();
+ self.0.try_to_tokens(&mut program)?;
+ let link_function_name = self.0.link_function_name(0);
+ let name = Ident::new(&link_function_name, Span::call_site());
+ let wasm_bindgen = &self.0.wasm_bindgen;
+ let abi_ret = quote! { #wasm_bindgen::convert::WasmRet<<#wasm_bindgen::__rt::alloc::string::String as #wasm_bindgen::convert::FromWasmAbi>::Abi> };
+ let extern_fn = extern_fn(&name, &[], &[], &[], abi_ret);
+ (quote! {
+ {
+ #program
+ #extern_fn
+
+ static __VAL: #wasm_bindgen::__rt::LazyLock<#wasm_bindgen::__rt::alloc::string::String> =
+ #wasm_bindgen::__rt::LazyLock::new(|| unsafe {
+ <#wasm_bindgen::__rt::alloc::string::String as #wasm_bindgen::convert::FromWasmAbi>::from_abi(#name().join())
+ });
+
+ #wasm_bindgen::__rt::alloc::string::String::clone(&__VAL)
+ }
+ })
+ .to_tokens(tokens);
+ Ok(())
+ }
+}
+
impl ToTokens for ast::Struct {
fn to_tokens(&self, tokens: &mut TokenStream) {
let name = &self.rust_name;
let name_str = self.js_name.to_string();
let name_len = name_str.len() as u32;
- let name_chars = name_str.chars().map(|c| c as u32);
+ let name_chars: Vec = name_str.chars().map(|c| c as u32).collect();
let new_fn = Ident::new(&shared::new_function(&name_str), Span::call_site());
let free_fn = Ident::new(&shared::free_function(&name_str), Span::call_site());
- let free_fn_const = Ident::new(&format!("{}__const", free_fn), free_fn.span());
+ let unwrap_fn = Ident::new(&shared::unwrap_function(&name_str), Span::call_site());
+ let wasm_bindgen = &self.wasm_bindgen;
(quote! {
#[automatically_derived]
- impl wasm_bindgen::describe::WasmDescribe for #name {
+ impl #wasm_bindgen::__rt::marker::SupportsConstructor for #name {}
+ #[automatically_derived]
+ impl #wasm_bindgen::__rt::marker::SupportsInstanceProperty for #name {}
+ #[automatically_derived]
+ impl #wasm_bindgen::__rt::marker::SupportsStaticProperty for #name {}
+
+ #[automatically_derived]
+ impl #wasm_bindgen::describe::WasmDescribe for #name {
fn describe() {
- use wasm_bindgen::__wbindgen_if_not_std;
- __wbindgen_if_not_std! {
- compile_error! {
- "exporting a class to JS requires the `std` feature to \
- be enabled in the `wasm-bindgen` crate"
- }
- }
- use wasm_bindgen::describe::*;
+ use #wasm_bindgen::describe::*;
inform(RUST_STRUCT);
inform(#name_len);
#(inform(#name_chars);)*
@@ -168,102 +242,223 @@ impl ToTokens for ast::Struct {
}
#[automatically_derived]
- impl wasm_bindgen::convert::IntoWasmAbi for #name {
+ impl #wasm_bindgen::convert::IntoWasmAbi for #name {
type Abi = u32;
fn into_abi(self) -> u32 {
- use wasm_bindgen::__rt::std::boxed::Box;
- use wasm_bindgen::__rt::WasmRefCell;
- Box::into_raw(Box::new(WasmRefCell::new(self))) as u32
+ use #wasm_bindgen::__rt::alloc::rc::Rc;
+ use #wasm_bindgen::__rt::WasmRefCell;
+ Rc::into_raw(Rc::new(WasmRefCell::new(self))) as u32
}
}
#[automatically_derived]
- impl wasm_bindgen::convert::FromWasmAbi for #name {
+ impl #wasm_bindgen::convert::FromWasmAbi for #name {
type Abi = u32;
unsafe fn from_abi(js: u32) -> Self {
- use wasm_bindgen::__rt::std::boxed::Box;
- use wasm_bindgen::__rt::{assert_not_null, WasmRefCell};
+ use #wasm_bindgen::__rt::alloc::rc::Rc;
+ use #wasm_bindgen::__rt::core::result::Result::{Ok, Err};
+ use #wasm_bindgen::__rt::{assert_not_null, WasmRefCell};
let ptr = js as *mut WasmRefCell<#name>;
assert_not_null(ptr);
- let js = Box::from_raw(ptr);
- (*js).borrow_mut(); // make sure no one's borrowing
- js.into_inner()
+ let rc = Rc::from_raw(ptr);
+ match Rc::try_unwrap(rc) {
+ Ok(cell) => cell.into_inner(),
+ Err(_) => #wasm_bindgen::throw_str(
+ "attempted to take ownership of Rust value while it was borrowed"
+ ),
+ }
}
}
#[automatically_derived]
- impl wasm_bindgen::__rt::core::convert::From<#name> for
- wasm_bindgen::JsValue
+ impl #wasm_bindgen::__rt::core::convert::From<#name> for
+ #wasm_bindgen::JsValue
{
fn from(value: #name) -> Self {
- let ptr = wasm_bindgen::convert::IntoWasmAbi::into_abi(value);
+ let ptr = #wasm_bindgen::convert::IntoWasmAbi::into_abi(value);
#[link(wasm_import_module = "__wbindgen_placeholder__")]
- #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
+ #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))]
extern "C" {
fn #new_fn(ptr: u32) -> u32;
}
- #[cfg(not(all(target_arch = "wasm32", not(target_os = "emscripten"))))]
+ #[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))))]
unsafe fn #new_fn(_: u32) -> u32 {
- panic!("cannot convert to JsValue outside of the wasm target")
+ panic!("cannot convert to JsValue outside of the Wasm target")
}
unsafe {
-
+ <#wasm_bindgen::JsValue as #wasm_bindgen::convert::FromWasmAbi>
::from_abi(#new_fn(ptr))
}
}
}
- #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
+ #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))]
#[automatically_derived]
- const #free_fn_const: () = {
+ const _: () = {
+ #wasm_bindgen::__wbindgen_coverage! {
#[no_mangle]
#[doc(hidden)]
- pub unsafe extern "C" fn #free_fn(ptr: u32) {
- drop(<#name as wasm_bindgen::convert::FromWasmAbi>::from_abi(ptr));
+ // `allow_delayed` is whether it's ok to not actually free the `ptr` immediately
+ // if it's still borrowed.
+ pub unsafe extern "C" fn #free_fn(ptr: u32, allow_delayed: u32) {
+ use #wasm_bindgen::__rt::alloc::rc::Rc;
+
+ if allow_delayed != 0 {
+ // Just drop the implicit `Rc` owned by JS, and then if the value is still
+ // referenced it'll be kept alive by its other `Rc`s.
+ let ptr = ptr as *mut #wasm_bindgen::__rt::WasmRefCell<#name>;
+ #wasm_bindgen::__rt::assert_not_null(ptr);
+ drop(Rc::from_raw(ptr));
+ } else {
+ // Claim ownership of the value, which will panic if it's borrowed.
+ let _ = <#name as #wasm_bindgen::convert::FromWasmAbi>::from_abi(ptr);
+ }
+ }
}
};
#[automatically_derived]
- impl wasm_bindgen::convert::RefFromWasmAbi for #name {
+ impl #wasm_bindgen::convert::RefFromWasmAbi for #name {
type Abi = u32;
- type Anchor = wasm_bindgen::__rt::Ref<'static, #name>;
+ type Anchor = #wasm_bindgen::__rt::RcRef<#name>;
unsafe fn ref_from_abi(js: Self::Abi) -> Self::Anchor {
- let js = js as *mut wasm_bindgen::__rt::WasmRefCell<#name>;
- wasm_bindgen::__rt::assert_not_null(js);
- (*js).borrow()
+ use #wasm_bindgen::__rt::alloc::rc::Rc;
+
+ let js = js as *mut #wasm_bindgen::__rt::WasmRefCell<#name>;
+ #wasm_bindgen::__rt::assert_not_null(js);
+
+ Rc::increment_strong_count(js);
+ let rc = Rc::from_raw(js);
+ #wasm_bindgen::__rt::RcRef::new(rc)
}
}
#[automatically_derived]
- impl wasm_bindgen::convert::RefMutFromWasmAbi for #name {
+ impl #wasm_bindgen::convert::RefMutFromWasmAbi for #name {
type Abi = u32;
- type Anchor = wasm_bindgen::__rt::RefMut<'static, #name>;
+ type Anchor = #wasm_bindgen::__rt::RcRefMut<#name>;
unsafe fn ref_mut_from_abi(js: Self::Abi) -> Self::Anchor {
- let js = js as *mut wasm_bindgen::__rt::WasmRefCell<#name>;
- wasm_bindgen::__rt::assert_not_null(js);
- (*js).borrow_mut()
+ use #wasm_bindgen::__rt::alloc::rc::Rc;
+
+ let js = js as *mut #wasm_bindgen::__rt::WasmRefCell<#name>;
+ #wasm_bindgen::__rt::assert_not_null(js);
+
+ Rc::increment_strong_count(js);
+ let rc = Rc::from_raw(js);
+ #wasm_bindgen::__rt::RcRefMut::new(rc)
+ }
+ }
+
+ #[automatically_derived]
+ impl #wasm_bindgen::convert::LongRefFromWasmAbi for #name {
+ type Abi = u32;
+ type Anchor = #wasm_bindgen::__rt::RcRef<#name>;
+
+ unsafe fn long_ref_from_abi(js: Self::Abi) -> Self::Anchor {
+ ::ref_from_abi(js)
}
}
#[automatically_derived]
- impl wasm_bindgen::convert::OptionIntoWasmAbi for #name {
+ impl #wasm_bindgen::convert::OptionIntoWasmAbi for #name {
#[inline]
fn none() -> Self::Abi { 0 }
}
#[automatically_derived]
- impl wasm_bindgen::convert::OptionFromWasmAbi for #name {
+ impl #wasm_bindgen::convert::OptionFromWasmAbi for #name {
#[inline]
fn is_none(abi: &Self::Abi) -> bool { *abi == 0 }
}
+
+ #[automatically_derived]
+ impl #wasm_bindgen::convert::TryFromJsValue for #name {
+ type Error = #wasm_bindgen::JsValue;
+
+ fn try_from_js_value(value: #wasm_bindgen::JsValue)
+ -> #wasm_bindgen::__rt::core::result::Result {
+ let idx = #wasm_bindgen::convert::IntoWasmAbi::into_abi(&value);
+
+ #[link(wasm_import_module = "__wbindgen_placeholder__")]
+ #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))]
+ extern "C" {
+ fn #unwrap_fn(ptr: u32) -> u32;
+ }
+
+ #[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))))]
+ unsafe fn #unwrap_fn(_: u32) -> u32 {
+ panic!("cannot convert from JsValue outside of the Wasm target")
+ }
+
+ let ptr = unsafe { #unwrap_fn(idx) };
+ if ptr == 0 {
+ #wasm_bindgen::__rt::core::result::Result::Err(value)
+ } else {
+ // Don't run `JsValue`'s destructor, `unwrap_fn` already did that for us.
+ #[allow(clippy::mem_forget)]
+ #wasm_bindgen::__rt::core::mem::forget(value);
+ unsafe {
+ #wasm_bindgen::__rt::core::result::Result::Ok(
+ ::from_abi(ptr)
+ )
+ }
+ }
+ }
+ }
+
+ #[automatically_derived]
+ impl #wasm_bindgen::describe::WasmDescribeVector for #name {
+ fn describe_vector() {
+ use #wasm_bindgen::describe::*;
+ inform(VECTOR);
+ inform(NAMED_EXTERNREF);
+ inform(#name_len);
+ #(inform(#name_chars);)*
+ }
+ }
+
+ #[automatically_derived]
+ impl #wasm_bindgen::convert::VectorIntoWasmAbi for #name {
+ type Abi = <
+ #wasm_bindgen::__rt::alloc::boxed::Box<[#wasm_bindgen::JsValue]>
+ as #wasm_bindgen::convert::IntoWasmAbi
+ >::Abi;
+
+ fn vector_into_abi(
+ vector: #wasm_bindgen::__rt::alloc::boxed::Box<[#name]>
+ ) -> Self::Abi {
+ #wasm_bindgen::convert::js_value_vector_into_abi(vector)
+ }
+ }
+
+ #[automatically_derived]
+ impl #wasm_bindgen::convert::VectorFromWasmAbi for #name {
+ type Abi = <
+ #wasm_bindgen::__rt::alloc::boxed::Box<[#wasm_bindgen::JsValue]>
+ as #wasm_bindgen::convert::FromWasmAbi
+ >::Abi;
+
+ unsafe fn vector_from_abi(
+ js: Self::Abi
+ ) -> #wasm_bindgen::__rt::alloc::boxed::Box<[#name]> {
+ #wasm_bindgen::convert::js_value_vector_from_abi(js)
+ }
+ }
+
+ #[automatically_derived]
+ impl #wasm_bindgen::__rt::VectorIntoJsValue for #name {
+ fn vector_into_jsvalue(vector: #wasm_bindgen::__rt::alloc::boxed::Box<[#name]>) -> #wasm_bindgen::JsValue {
+ #wasm_bindgen::__rt::js_value_vector_into_jsvalue(vector)
+ }
+ }
})
.to_tokens(tokens);
@@ -281,50 +476,59 @@ impl ToTokens for ast::StructField {
let getter = &self.getter;
let setter = &self.setter;
- let maybe_assert_copy = if self.getter_with_clone {
+ let maybe_assert_copy = if self.getter_with_clone.is_some() {
quote! {}
} else {
quote! { assert_copy::<#ty>() }
};
let maybe_assert_copy = respan(maybe_assert_copy, ty);
- let maybe_clone = if self.getter_with_clone {
- quote! { .clone() }
- } else {
- quote! {}
- };
+ // Split this out so that it isn't affected by `quote_spanned!`.
+ //
+ // If we don't do this, it might end up being unable to reference `js`
+ // properly because it doesn't have the same span.
+ //
+ // See https://github.com/rustwasm/wasm-bindgen/pull/3725.
+ let js_token = quote! { js };
+ let mut val = quote_spanned!(self.rust_name.span()=> (*#js_token).borrow().#rust_name);
+ if let Some(span) = self.getter_with_clone {
+ val = quote_spanned!(span=> <#ty as Clone>::clone(val) );
+ }
- let getter_const = Ident::new(&format!("{}__const", getter), getter.span());
+ let wasm_bindgen = &self.wasm_bindgen;
(quote! {
#[automatically_derived]
- const #getter_const: () = {
- #[cfg_attr(all(target_arch = "wasm32", not(target_os = "emscripten")), no_mangle)]
+ const _: () = {
+ #wasm_bindgen::__wbindgen_coverage! {
+ #[cfg_attr(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")), no_mangle)]
#[doc(hidden)]
pub unsafe extern "C" fn #getter(js: u32)
- -> <#ty as wasm_bindgen::convert::IntoWasmAbi>::Abi
+ -> #wasm_bindgen::convert::WasmRet<<#ty as #wasm_bindgen::convert::IntoWasmAbi>::Abi>
{
- use wasm_bindgen::__rt::{WasmRefCell, assert_not_null};
- use wasm_bindgen::convert::IntoWasmAbi;
+ use #wasm_bindgen::__rt::{WasmRefCell, assert_not_null};
+ use #wasm_bindgen::convert::IntoWasmAbi;
fn assert_copy(){}
#maybe_assert_copy;
let js = js as *mut WasmRefCell<#struct_name>;
assert_not_null(js);
- let val = (*js).borrow().#rust_name#maybe_clone;
- <#ty as IntoWasmAbi>::into_abi(val)
+ let val = #val;
+ <#ty as IntoWasmAbi>::into_abi(val).into()
+ }
}
};
})
.to_tokens(tokens);
Descriptor {
- ident: &getter,
+ ident: getter,
inner: quote! {
<#ty as WasmDescribe>::describe();
},
attrs: vec![],
+ wasm_bindgen: &self.wasm_bindgen,
}
.to_tokens(tokens);
@@ -332,26 +536,30 @@ impl ToTokens for ast::StructField {
return;
}
- let setter_const = Ident::new(&format!("{}__const", setter), setter.span());
+ let abi = quote! { <#ty as #wasm_bindgen::convert::FromWasmAbi>::Abi };
+ let (args, names) = splat(wasm_bindgen, &Ident::new("val", rust_name.span()), &abi);
(quote! {
- #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
+ #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))]
#[automatically_derived]
- const #setter_const: () = {
+ const _: () = {
+ #wasm_bindgen::__wbindgen_coverage! {
#[no_mangle]
#[doc(hidden)]
pub unsafe extern "C" fn #setter(
js: u32,
- val: <#ty as wasm_bindgen::convert::FromWasmAbi>::Abi,
+ #(#args,)*
) {
- use wasm_bindgen::__rt::{WasmRefCell, assert_not_null};
- use wasm_bindgen::convert::FromWasmAbi;
+ use #wasm_bindgen::__rt::{WasmRefCell, assert_not_null};
+ use #wasm_bindgen::convert::FromWasmAbi;
let js = js as *mut WasmRefCell<#struct_name>;
assert_not_null(js);
+ let val = <#abi as #wasm_bindgen::convert::WasmAbi>::join(#(#names),*);
let val = <#ty as FromWasmAbi>::from_abi(val);
(*js).borrow_mut().#rust_name = val;
}
+ }
};
})
.to_tokens(tokens);
@@ -375,12 +583,14 @@ impl TryToTokens for ast::Export {
};
let name = &self.rust_name;
+ let wasm_bindgen = &self.wasm_bindgen;
+ let wasm_bindgen_futures = &self.wasm_bindgen_futures;
let receiver = match self.method_self {
Some(ast::MethodSelf::ByValue) => {
let class = self.rust_class.as_ref().unwrap();
arg_conversions.push(quote! {
let me = unsafe {
- <#class as wasm_bindgen::convert::FromWasmAbi>::from_abi(me)
+ <#class as #wasm_bindgen::convert::FromWasmAbi>::from_abi(me)
};
});
quote! { me.#name }
@@ -389,7 +599,7 @@ impl TryToTokens for ast::Export {
let class = self.rust_class.as_ref().unwrap();
arg_conversions.push(quote! {
let mut me = unsafe {
- <#class as wasm_bindgen::convert::RefMutFromWasmAbi>
+ <#class as #wasm_bindgen::convert::RefMutFromWasmAbi>
::ref_mut_from_abi(me)
};
let me = &mut *me;
@@ -398,12 +608,24 @@ impl TryToTokens for ast::Export {
}
Some(ast::MethodSelf::RefShared) => {
let class = self.rust_class.as_ref().unwrap();
+ let (trait_, func, borrow) = if self.function.r#async {
+ (
+ quote!(LongRefFromWasmAbi),
+ quote!(long_ref_from_abi),
+ quote!(
+ <<#class as #wasm_bindgen::convert::LongRefFromWasmAbi>
+ ::Anchor as #wasm_bindgen::__rt::core::borrow::Borrow<#class>>
+ ::borrow(&me)
+ ),
+ )
+ } else {
+ (quote!(RefFromWasmAbi), quote!(ref_from_abi), quote!(&*me))
+ };
arg_conversions.push(quote! {
let me = unsafe {
- <#class as wasm_bindgen::convert::RefFromWasmAbi>
- ::ref_from_abi(me)
+ <#class as #wasm_bindgen::convert::#trait_>::#func(me)
};
- let me = &*me;
+ let me = #borrow;
});
quote! { me.#name }
}
@@ -415,47 +637,79 @@ impl TryToTokens for ast::Export {
let mut argtys = Vec::new();
for (i, arg) in self.function.arguments.iter().enumerate() {
- argtys.push(&arg.ty);
+ argtys.push(&*arg.pat_type.ty);
let i = i + offset;
let ident = Ident::new(&format!("arg{}", i), Span::call_site());
- let ty = &arg.ty;
- match &*arg.ty {
+ fn unwrap_nested_types(ty: &syn::Type) -> &syn::Type {
+ match &ty {
+ syn::Type::Group(syn::TypeGroup { ref elem, .. }) => unwrap_nested_types(elem),
+ syn::Type::Paren(syn::TypeParen { ref elem, .. }) => unwrap_nested_types(elem),
+ _ => ty,
+ }
+ }
+ let ty = unwrap_nested_types(&arg.pat_type.ty);
+
+ match &ty {
syn::Type::Reference(syn::TypeReference {
mutability: Some(_),
elem,
..
}) => {
- args.push(quote! {
- #ident: <#elem as wasm_bindgen::convert::RefMutFromWasmAbi>::Abi
- });
+ let abi = quote! { <#elem as #wasm_bindgen::convert::RefMutFromWasmAbi>::Abi };
+ let (prim_args, prim_names) = splat(wasm_bindgen, &ident, &abi);
+ args.extend(prim_args);
arg_conversions.push(quote! {
let mut #ident = unsafe {
- <#elem as wasm_bindgen::convert::RefMutFromWasmAbi>
- ::ref_mut_from_abi(#ident)
+ <#elem as #wasm_bindgen::convert::RefMutFromWasmAbi>
+ ::ref_mut_from_abi(
+ <#abi as #wasm_bindgen::convert::WasmAbi>::join(#(#prim_names),*)
+ )
};
let #ident = &mut *#ident;
});
}
syn::Type::Reference(syn::TypeReference { elem, .. }) => {
- args.push(quote! {
- #ident: <#elem as wasm_bindgen::convert::RefFromWasmAbi>::Abi
- });
- arg_conversions.push(quote! {
- let #ident = unsafe {
- <#elem as wasm_bindgen::convert::RefFromWasmAbi>
- ::ref_from_abi(#ident)
- };
- let #ident = &*#ident;
- });
+ if self.function.r#async {
+ let abi =
+ quote! { <#elem as #wasm_bindgen::convert::LongRefFromWasmAbi>::Abi };
+ let (prim_args, prim_names) = splat(wasm_bindgen, &ident, &abi);
+ args.extend(prim_args);
+ arg_conversions.push(quote! {
+ let #ident = unsafe {
+ <#elem as #wasm_bindgen::convert::LongRefFromWasmAbi>
+ ::long_ref_from_abi(
+ <#abi as #wasm_bindgen::convert::WasmAbi>::join(#(#prim_names),*)
+ )
+ };
+ let #ident = <<#elem as #wasm_bindgen::convert::LongRefFromWasmAbi>
+ ::Anchor as core::borrow::Borrow<#elem>>
+ ::borrow(ident);
+ });
+ } else {
+ let abi = quote! { <#elem as #wasm_bindgen::convert::RefFromWasmAbi>::Abi };
+ let (prim_args, prim_names) = splat(wasm_bindgen, &ident, &abi);
+ args.extend(prim_args);
+ arg_conversions.push(quote! {
+ let #ident = unsafe {
+ <#elem as #wasm_bindgen::convert::RefFromWasmAbi>
+ ::ref_from_abi(
+ <#abi as #wasm_bindgen::convert::WasmAbi>::join(#(#prim_names),*)
+ )
+ };
+ let #ident = &*#ident;
+ });
+ }
}
_ => {
- args.push(quote! {
- #ident: <#ty as wasm_bindgen::convert::FromWasmAbi>::Abi
- });
+ let abi = quote! { <#ty as #wasm_bindgen::convert::FromWasmAbi>::Abi };
+ let (prim_args, prim_names) = splat(wasm_bindgen, &ident, &abi);
+ args.extend(prim_args);
arg_conversions.push(quote! {
let #ident = unsafe {
- <#ty as wasm_bindgen::convert::FromWasmAbi>
- ::from_abi(#ident)
+ <#ty as #wasm_bindgen::convert::FromWasmAbi>
+ ::from_abi(
+ <#abi as #wasm_bindgen::convert::WasmAbi>::join(#(#prim_names),*)
+ )
};
});
}
@@ -466,7 +720,12 @@ impl TryToTokens for ast::Export {
elems: Default::default(),
paren_token: Default::default(),
});
- let syn_ret = self.function.ret.as_ref().unwrap_or(&syn_unit);
+ let syn_ret = self
+ .function
+ .ret
+ .as_ref()
+ .map(|ret| &ret.r#type)
+ .unwrap_or(&syn_unit);
if let syn::Type::Reference(_) = syn_ret {
bail_span!(syn_ret, "cannot return a borrowed ref with #[wasm_bindgen]",)
}
@@ -480,19 +739,15 @@ impl TryToTokens for ast::Export {
quote! { () },
quote! { () },
quote! {
- wasm_bindgen_futures::spawn_local(async move {
- <#syn_ret as wasm_bindgen::__rt::Start>::start(#ret.await);
- })
+ <#syn_ret as #wasm_bindgen::__rt::Start>::start(#ret.await)
},
)
} else {
(
- quote! { wasm_bindgen::JsValue },
+ quote! { #wasm_bindgen::JsValue },
quote! { #syn_ret },
quote! {
- wasm_bindgen_futures::future_to_promise(async move {
- <#syn_ret as wasm_bindgen::__rt::IntoJsResult>::into_js_result(#ret.await)
- }).into()
+ <#syn_ret as #wasm_bindgen::__rt::IntoJsResult>::into_js_result(#ret.await)
},
)
}
@@ -500,14 +755,38 @@ impl TryToTokens for ast::Export {
(
quote! { () },
quote! { () },
- quote! { <#syn_ret as wasm_bindgen::__rt::Start>::start(#ret) },
+ quote! { <#syn_ret as #wasm_bindgen::__rt::Start>::start(#ret) },
)
} else {
(quote! { #syn_ret }, quote! { #syn_ret }, quote! { #ret })
};
- let projection = quote! { <#ret_ty as wasm_bindgen::convert::ReturnWasmAbi> };
- let convert_ret = quote! { #projection::return_abi(#ret_expr) };
+ let mut call = quote! {
+ {
+ #(#arg_conversions)*
+ let #ret = #receiver(#(#converted_arguments),*);
+ #ret_expr
+ }
+ };
+
+ if self.function.r#async {
+ if self.start {
+ call = quote! {
+ #wasm_bindgen_futures::spawn_local(async move {
+ #call
+ })
+ }
+ } else {
+ call = quote! {
+ #wasm_bindgen_futures::future_to_promise(async move {
+ #call
+ }).into()
+ }
+ }
+ }
+
+ let projection = quote! { <#ret_ty as #wasm_bindgen::convert::ReturnWasmAbi> };
+ let convert_ret = quote! { #projection::return_abi(#ret).into() };
let describe_ret = quote! {
<#ret_ty as WasmDescribe>::describe();
<#inner_ret_ty as WasmDescribe>::describe();
@@ -515,37 +794,79 @@ impl TryToTokens for ast::Export {
let nargs = self.function.arguments.len() as u32;
let attrs = &self.function.rust_attrs;
- let start_check = if self.start {
- quote! { const _ASSERT: fn() = || -> #projection::Abi { loop {} }; }
- } else {
- quote! {}
+ let mut checks = Vec::new();
+ if self.start {
+ checks.push(quote! { const _ASSERT: fn() = || -> #projection::Abi { loop {} }; });
};
- let generated_name_const =
- Ident::new(&format!("{}__const", generated_name), generated_name.span());
+ if let Some(class) = self.rust_class.as_ref() {
+ // little helper function to make sure the check points to the
+ // location of the function causing the assert to fail
+ let mut add_check = |token_stream| {
+ checks.push(respan(token_stream, &self.rust_name));
+ };
+
+ match &self.method_kind {
+ ast::MethodKind::Constructor => {
+ add_check(quote! {
+ let _: #wasm_bindgen::__rt::marker::CheckSupportsConstructor<#class>;
+ });
+ }
+ ast::MethodKind::Operation(operation) => match operation.kind {
+ ast::OperationKind::Getter(_) | ast::OperationKind::Setter(_) => {
+ if operation.is_static {
+ add_check(quote! {
+ let _: #wasm_bindgen::__rt::marker::CheckSupportsStaticProperty<#class>;
+ });
+ } else {
+ add_check(quote! {
+ let _: #wasm_bindgen::__rt::marker::CheckSupportsInstanceProperty<#class>;
+ });
+ }
+ }
+ _ => {}
+ },
+ }
+ }
+
(quote! {
#[automatically_derived]
- const #generated_name_const: () = {
+ const _: () = {
+ #wasm_bindgen::__wbindgen_coverage! {
#(#attrs)*
#[cfg_attr(
- all(target_arch = "wasm32", not(target_os = "emscripten")),
+ all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")),
export_name = #export_name,
)]
- pub extern "C" fn #generated_name(#(#args),*) -> #projection::Abi {
- #start_check
- // Scope all local variables to be destroyed after we call
- // the function to ensure that `#convert_ret`, if it panics,
- // doesn't leak anything.
- let #ret = {
- #(#arg_conversions)*
- #receiver(#(#converted_arguments),*)
+ pub unsafe extern "C" fn #generated_name(#(#args),*) -> #wasm_bindgen::convert::WasmRet<#projection::Abi> {
+ const _: () = {
+ #(#checks)*
};
+
+ let #ret = #call;
#convert_ret
}
+ }
};
})
.to_tokens(into);
+ let describe_args: TokenStream = argtys
+ .iter()
+ .map(|ty| match ty {
+ syn::Type::Reference(reference)
+ if self.function.r#async && reference.mutability.is_none() =>
+ {
+ let inner = &reference.elem;
+ quote! {
+ inform(LONGREF);
+ <#inner as WasmDescribe>::describe();
+ }
+ }
+ _ => quote! { <#ty as WasmDescribe>::describe(); },
+ })
+ .collect();
+
// In addition to generating the shim function above which is what
// our generated JS will invoke, we *also* generate a "descriptor"
// shim. This descriptor shim uses the `WasmDescribe` trait to
@@ -569,10 +890,11 @@ impl TryToTokens for ast::Export {
inform(FUNCTION);
inform(0);
inform(#nargs);
- #(<#argtys as WasmDescribe>::describe();)*
+ #describe_args
#describe_ret
},
attrs: attrs.clone(),
+ wasm_bindgen: &self.wasm_bindgen,
}
.to_tokens(into);
@@ -585,6 +907,7 @@ impl TryToTokens for ast::ImportKind {
match *self {
ast::ImportKind::Function(ref f) => f.try_to_tokens(tokens)?,
ast::ImportKind::Static(ref s) => s.to_tokens(tokens),
+ ast::ImportKind::String(ref s) => s.to_tokens(tokens),
ast::ImportKind::Type(ref t) => t.to_tokens(tokens),
ast::ImportKind::Enum(ref e) => e.to_tokens(tokens),
}
@@ -602,16 +925,15 @@ impl ToTokens for ast::ImportType {
None => "",
Some(comment) => comment,
};
- let const_name = format!("__wbg_generated_const_{}", rust_name);
- let const_name = Ident::new(&const_name, Span::call_site());
let instanceof_shim = Ident::new(&self.instanceof_shim, Span::call_site());
+ let wasm_bindgen = &self.wasm_bindgen;
let internal_obj = match self.extends.first() {
Some(target) => {
quote! { #target }
}
None => {
- quote! { wasm_bindgen::JsValue }
+ quote! { #wasm_bindgen::JsValue }
}
};
@@ -619,7 +941,7 @@ impl ToTokens for ast::ImportType {
let typescript_type_len = typescript_type.len() as u32;
let typescript_type_chars = typescript_type.chars().map(|c| c as u32);
quote! {
- use wasm_bindgen::describe::*;
+ use #wasm_bindgen::describe::*;
inform(NAMED_EXTERNREF);
inform(#typescript_type_len);
#(inform(#typescript_type_chars);)*
@@ -642,30 +964,41 @@ impl ToTokens for ast::ImportType {
let no_deref = self.no_deref;
+ let doc = if doc_comment.is_empty() {
+ quote! {}
+ } else {
+ quote! {
+ #[doc = #doc_comment]
+ }
+ };
+
(quote! {
#[automatically_derived]
#(#attrs)*
- #[doc = #doc_comment]
+ #doc
#[repr(transparent)]
#vis struct #rust_name {
obj: #internal_obj
}
#[automatically_derived]
- const #const_name: () = {
- use wasm_bindgen::convert::{IntoWasmAbi, FromWasmAbi};
- use wasm_bindgen::convert::{OptionIntoWasmAbi, OptionFromWasmAbi};
- use wasm_bindgen::convert::RefFromWasmAbi;
- use wasm_bindgen::describe::WasmDescribe;
- use wasm_bindgen::{JsValue, JsCast, JsObject};
- use wasm_bindgen::__rt::core;
+ const _: () = {
+ use #wasm_bindgen::convert::TryFromJsValue;
+ use #wasm_bindgen::convert::{IntoWasmAbi, FromWasmAbi};
+ use #wasm_bindgen::convert::{OptionIntoWasmAbi, OptionFromWasmAbi};
+ use #wasm_bindgen::convert::{RefFromWasmAbi, LongRefFromWasmAbi};
+ use #wasm_bindgen::describe::WasmDescribe;
+ use #wasm_bindgen::{JsValue, JsCast, JsObject};
+ use #wasm_bindgen::__rt::core;
+ #[automatically_derived]
impl WasmDescribe for #rust_name {
fn describe() {
#description
}
}
+ #[automatically_derived]
impl IntoWasmAbi for #rust_name {
type Abi = ::Abi;
@@ -675,6 +1008,7 @@ impl ToTokens for ast::ImportType {
}
}
+ #[automatically_derived]
impl OptionIntoWasmAbi for #rust_name {
#[inline]
fn none() -> Self::Abi {
@@ -682,6 +1016,7 @@ impl ToTokens for ast::ImportType {
}
}
+ #[automatically_derived]
impl<'a> OptionIntoWasmAbi for &'a #rust_name {
#[inline]
fn none() -> Self::Abi {
@@ -689,6 +1024,7 @@ impl ToTokens for ast::ImportType {
}
}
+ #[automatically_derived]
impl FromWasmAbi for #rust_name {
type Abi = ::Abi;
@@ -700,11 +1036,13 @@ impl ToTokens for ast::ImportType {
}
}
+ #[automatically_derived]
impl OptionFromWasmAbi for #rust_name {
#[inline]
fn is_none(abi: &Self::Abi) -> bool { *abi == 0 }
}
+ #[automatically_derived]
impl<'a> IntoWasmAbi for &'a #rust_name {
type Abi = <&'a JsValue as IntoWasmAbi>::Abi;
@@ -714,6 +1052,7 @@ impl ToTokens for ast::ImportType {
}
}
+ #[automatically_derived]
impl RefFromWasmAbi for #rust_name {
type Abi = ::Abi;
type Anchor = core::mem::ManuallyDrop<#rust_name>;
@@ -727,7 +1066,20 @@ impl ToTokens for ast::ImportType {
}
}
+ #[automatically_derived]
+ impl LongRefFromWasmAbi for #rust_name {
+ type Abi = ::Abi;
+ type Anchor = #rust_name;
+
+ #[inline]
+ unsafe fn long_ref_from_abi(js: Self::Abi) -> Self::Anchor {
+ let tmp = ::long_ref_from_abi(js);
+ #rust_name { obj: tmp.into() }
+ }
+ }
+
// TODO: remove this on the next major version
+ #[automatically_derived]
impl From for #rust_name {
#[inline]
fn from(obj: JsValue) -> #rust_name {
@@ -735,17 +1087,20 @@ impl ToTokens for ast::ImportType {
}
}
+ #[automatically_derived]
impl AsRef for #rust_name {
#[inline]
fn as_ref(&self) -> &JsValue { self.obj.as_ref() }
}
+ #[automatically_derived]
impl AsRef<#rust_name> for #rust_name {
#[inline]
fn as_ref(&self) -> rust_name { self }
}
+ #[automatically_derived]
impl From<#rust_name> for JsValue {
#[inline]
fn from(obj: #rust_name) -> JsValue {
@@ -753,14 +1108,15 @@ impl ToTokens for ast::ImportType {
}
}
+ #[automatically_derived]
impl JsCast for #rust_name {
fn instanceof(val: &JsValue) -> bool {
#[link(wasm_import_module = "__wbindgen_placeholder__")]
- #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
+ #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))]
extern "C" {
fn #instanceof_shim(val: u32) -> u32;
}
- #[cfg(not(all(target_arch = "wasm32", not(target_os = "emscripten"))))]
+ #[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))))]
unsafe fn #instanceof_shim(_: u32) -> u32 {
panic!("cannot check instanceof on non-wasm targets");
}
@@ -811,7 +1167,7 @@ impl ToTokens for ast::ImportType {
impl From<#rust_name> for #superclass {
#[inline]
fn from(obj: #rust_name) -> #superclass {
- use wasm_bindgen::JsCast;
+ use #wasm_bindgen::JsCast;
#superclass::unchecked_from_js(obj.into())
}
}
@@ -820,7 +1176,7 @@ impl ToTokens for ast::ImportType {
impl AsRef<#superclass> for #rust_name {
#[inline]
fn as_ref(&self) -> superclass {
- use wasm_bindgen::JsCast;
+ use #wasm_bindgen::JsCast;
#superclass::unchecked_from_js_ref(self.as_ref())
}
}
@@ -830,115 +1186,126 @@ impl ToTokens for ast::ImportType {
}
}
-impl ToTokens for ast::ImportEnum {
+impl ToTokens for ast::StringEnum {
fn to_tokens(&self, tokens: &mut TokenStream) {
let vis = &self.vis;
- let name = &self.name;
- let expect_string = format!("attempted to convert invalid {} into JSValue", name);
+ let enum_name = &self.name;
+ let name_str = &self.js_name;
+ let name_len = name_str.len() as u32;
+ let name_chars = name_str.chars().map(u32::from);
let variants = &self.variants;
- let variant_strings = &self.variant_values;
+ let variant_count = self.variant_values.len() as u32;
+ let variant_values = &self.variant_values;
+ let variant_indices = (0..variant_count).collect::>();
+ let invalid = variant_count;
+ let hole = variant_count + 1;
let attrs = &self.rust_attrs;
- let mut current_idx: usize = 0;
- let variant_indexes: Vec = variants
- .iter()
- .map(|_| {
- let this_index = current_idx;
- current_idx += 1;
- Literal::usize_unsuffixed(this_index)
- })
- .collect();
-
- // Borrow variant_indexes because we need to use it multiple times inside the quote! macro
- let variant_indexes_ref = &variant_indexes;
+ let invalid_to_str_msg = format!(
+ "Converting an invalid string enum ({}) back to a string is currently not supported",
+ enum_name
+ );
// A vector of EnumName::VariantName tokens for this enum
let variant_paths: Vec = self
.variants
.iter()
- .map(|v| quote!(#name::#v).into_token_stream())
+ .map(|v| quote!(#enum_name::#v).into_token_stream())
.collect();
// Borrow variant_paths because we need to use it multiple times inside the quote! macro
let variant_paths_ref = &variant_paths;
+ let wasm_bindgen = &self.wasm_bindgen;
+
(quote! {
#(#attrs)*
- #vis enum #name {
- #(#variants = #variant_indexes_ref,)*
+ #[non_exhaustive]
+ #[repr(u32)]
+ #vis enum #enum_name {
+ #(#variants = #variant_indices,)*
#[automatically_derived]
#[doc(hidden)]
- __Nonexhaustive,
+ __Invalid
}
#[automatically_derived]
- impl #name {
- fn from_str(s: &str) -> Option<#name> {
+ impl #enum_name {
+ fn from_str(s: &str) -> Option<#enum_name> {
match s {
- #(#variant_strings => Some(#variant_paths_ref),)*
+ #(#variant_values => Some(#variant_paths_ref),)*
_ => None,
}
}
fn to_str(&self) -> &'static str {
match self {
- #(#variant_paths_ref => #variant_strings,)*
- #name::__Nonexhaustive => panic!(#expect_string),
+ #(#variant_paths_ref => #variant_values,)*
+ #enum_name::__Invalid => panic!(#invalid_to_str_msg),
}
}
- #vis fn from_js_value(obj: &wasm_bindgen::JsValue) -> Option<#name> {
+ #vis fn from_js_value(obj: wasm_bindgen::JsValue) -> Option<#enum_name> {
obj.as_string().and_then(|obj_str| Self::from_str(obj_str.as_str()))
}
}
- // It should really be using &str for all of these, but that requires some major changes to cli-support
- #[automatically_derived]
- impl wasm_bindgen::describe::WasmDescribe for #name {
- fn describe() {
- ::describe()
- }
- }
-
#[automatically_derived]
- impl wasm_bindgen::convert::IntoWasmAbi for #name {
- type Abi = ::Abi;
+ impl #wasm_bindgen::convert::IntoWasmAbi for #enum_name {
+ type Abi = u32;
#[inline]
- fn into_abi(self) -> Self::Abi {
- ::into_abi(self.into())
+ fn into_abi(self) -> u32 {
+ self as u32
}
}
#[automatically_derived]
- impl wasm_bindgen::convert::FromWasmAbi for #name {
- type Abi = ::Abi;
+ impl #wasm_bindgen::convert::FromWasmAbi for #enum_name {
+ type Abi = u32;
- unsafe fn from_abi(js: Self::Abi) -> Self {
- let s = ::from_abi(js);
- #name::from_js_value(&s).unwrap_or(#name::__Nonexhaustive)
+ unsafe fn from_abi(val: u32) -> Self {
+ match val {
+ #(#variant_indices => #variant_paths_ref,)*
+ #invalid => #enum_name::__Invalid,
+ _ => unreachable!("The JS binding should only ever produce a valid value or the specific 'invalid' value"),
+ }
}
}
#[automatically_derived]
- impl wasm_bindgen::convert::OptionIntoWasmAbi for #name {
+ impl #wasm_bindgen::convert::OptionFromWasmAbi for #enum_name {
#[inline]
- fn none() -> Self::Abi { <::js_sys::Object as wasm_bindgen::convert::OptionIntoWasmAbi>::none() }
+ fn is_none(val: &u32) -> bool { *val == #hole }
}
#[automatically_derived]
- impl wasm_bindgen::convert::OptionFromWasmAbi for #name {
+ impl #wasm_bindgen::convert::OptionIntoWasmAbi for #enum_name {
#[inline]
- fn is_none(abi: &Self::Abi) -> bool { <::js_sys::Object as wasm_bindgen::convert::OptionFromWasmAbi>::is_none(abi) }
+ fn none() -> Self::Abi { #hole }
+ }
+
+ #[automatically_derived]
+ impl #wasm_bindgen::describe::WasmDescribe for #enum_name {
+ fn describe() {
+ use #wasm_bindgen::describe::*;
+ inform(STRING_ENUM);
+ inform(#name_len);
+ #(inform(#name_chars);)*
+ inform(#variant_count);
+ }
}
#[automatically_derived]
- impl From<#name> for wasm_bindgen::JsValue {
- fn from(obj: #name) -> wasm_bindgen::JsValue {
- wasm_bindgen::JsValue::from(obj.to_str())
+ impl #wasm_bindgen::__rt::core::convert::From<#enum_name> for
+ #wasm_bindgen::JsValue
+ {
+ fn from(val: #enum_name) -> Self {
+ #wasm_bindgen::JsValue::from_str(val.to_str())
}
}
- }).to_tokens(tokens);
+ })
+ .to_tokens(tokens);
}
}
@@ -961,7 +1328,7 @@ impl TryToTokens for ast::ImportFunction {
ast::ImportFunctionKind::Normal => {}
}
let vis = &self.function.rust_vis;
- let ret = match &self.function.ret {
+ let ret = match self.function.ret.as_ref().map(|ret| &ret.r#type) {
Some(ty) => quote! { -> #ty },
None => quote!(),
};
@@ -971,10 +1338,12 @@ impl TryToTokens for ast::ImportFunction {
let mut arg_conversions = Vec::new();
let mut arguments = Vec::new();
let ret_ident = Ident::new("_ret", Span::call_site());
+ let wasm_bindgen = &self.wasm_bindgen;
+ let wasm_bindgen_futures = &self.wasm_bindgen_futures;
for (i, arg) in self.function.arguments.iter().enumerate() {
- let ty = &arg.ty;
- let name = match &*arg.pat {
+ let ty = &arg.pat_type.ty;
+ let name = match &*arg.pat_type.pat {
syn::Pat::Ident(syn::PatIdent {
by_ref: None,
ident,
@@ -983,15 +1352,16 @@ impl TryToTokens for ast::ImportFunction {
}) => ident.clone(),
syn::Pat::Wild(_) => syn::Ident::new(&format!("__genarg_{}", i), Span::call_site()),
_ => bail_span!(
- arg.pat,
+ arg.pat_type.pat,
"unsupported pattern in #[wasm_bindgen] imported function",
),
};
- abi_argument_names.push(name.clone());
- abi_arguments.push(quote! {
- #name: <#ty as wasm_bindgen::convert::IntoWasmAbi>::Abi
- });
+ let abi = quote! { <#ty as #wasm_bindgen::convert::IntoWasmAbi>::Abi };
+ let (prim_args, prim_names) = splat(wasm_bindgen, &name, &abi);
+ abi_arguments.extend(prim_args);
+ abi_argument_names.extend(prim_names.iter().cloned());
+
let var = if i == 0 && is_method {
quote! { self }
} else {
@@ -999,8 +1369,9 @@ impl TryToTokens for ast::ImportFunction {
quote! { #name }
};
arg_conversions.push(quote! {
- let #name = <#ty as wasm_bindgen::convert::IntoWasmAbi>
+ let #name = <#ty as #wasm_bindgen::convert::IntoWasmAbi>
::into_abi(#var);
+ let (#(#prim_names),*) = <#abi as #wasm_bindgen::convert::WasmAbi>::split(#name);
});
}
let abi_ret;
@@ -1014,37 +1385,39 @@ impl TryToTokens for ast::ImportFunction {
}
Some(ref ty) => {
if self.function.r#async {
- abi_ret =
- quote! { ::Abi };
+ abi_ret = quote! {
+ #wasm_bindgen::convert::WasmRet<<#wasm_bindgen_futures::js_sys::Promise as #wasm_bindgen::convert::FromWasmAbi>::Abi>
+ };
let future = quote! {
- wasm_bindgen_futures::JsFuture::from(
-
- ::from_abi(#ret_ident)
+ #wasm_bindgen_futures::JsFuture::from(
+ <#wasm_bindgen_futures::js_sys::Promise as #wasm_bindgen::convert::FromWasmAbi>
+ ::from_abi(#ret_ident.join())
).await
};
convert_ret = if self.catch {
- quote! { Ok(#future?) }
+ quote! { Ok(#wasm_bindgen::JsCast::unchecked_from_js(#future?)) }
} else {
- quote! { #future.expect("unexpected exception") }
+ quote! { #wasm_bindgen::JsCast::unchecked_from_js(#future.expect("unexpected exception")) }
};
} else {
abi_ret = quote! {
- <#ty as wasm_bindgen::convert::FromWasmAbi>::Abi
+ #wasm_bindgen::convert::WasmRet<<#ty as #wasm_bindgen::convert::FromWasmAbi>::Abi>
};
convert_ret = quote! {
- <#ty as wasm_bindgen::convert::FromWasmAbi>
- ::from_abi(#ret_ident)
+ <#ty as #wasm_bindgen::convert::FromWasmAbi>
+ ::from_abi(#ret_ident.join())
};
}
}
None => {
if self.function.r#async {
- abi_ret =
- quote! { ::Abi };
+ abi_ret = quote! {
+ #wasm_bindgen::convert::WasmRet<<#wasm_bindgen_futures::js_sys::Promise as #wasm_bindgen::convert::FromWasmAbi>::Abi>
+ };
let future = quote! {
- wasm_bindgen_futures::JsFuture::from(
-
- ::from_abi(#ret_ident)
+ #wasm_bindgen_futures::JsFuture::from(
+ <#wasm_bindgen_futures::js_sys::Promise as #wasm_bindgen::convert::FromWasmAbi>
+ ::from_abi(#ret_ident.join())
).await
};
convert_ret = if self.catch {
@@ -1063,7 +1436,7 @@ impl TryToTokens for ast::ImportFunction {
if self.catch && !self.function.r#async {
convert_ret = quote! { Ok(#convert_ret) };
exceptional_ret = quote! {
- wasm_bindgen::__rt::take_last_exception()?;
+ #wasm_bindgen::__rt::take_last_exception()?;
};
}
@@ -1071,12 +1444,14 @@ impl TryToTokens for ast::ImportFunction {
let import_name = &self.shim;
let attrs = &self.function.rust_attrs;
let arguments = &arguments;
- let abi_arguments = &abi_arguments;
- let abi_argument_names = &abi_argument_names;
+ let abi_arguments = &abi_arguments[..];
+ let abi_argument_names = &abi_argument_names[..];
- let doc_comment = match &self.doc_comment {
- None => "",
- Some(doc_string) => doc_string,
+ let doc = if self.doc_comment.is_empty() {
+ quote! {}
+ } else {
+ let doc_comment = &self.doc_comment;
+ quote! { #[doc = #doc_comment] }
};
let me = if is_method {
quote! { &self, }
@@ -1100,26 +1475,21 @@ impl TryToTokens for ast::ImportFunction {
// like rustc itself doesn't do great in that regard so let's just do
// the best we can in the meantime.
let extern_fn = respan(
- quote! {
- #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
- #(#attrs)*
- #[link(wasm_import_module = "__wbindgen_placeholder__")]
- extern "C" {
- fn #import_name(#(#abi_arguments),*) -> #abi_ret;
- }
-
- #[cfg(not(all(target_arch = "wasm32", not(target_os = "emscripten"))))]
- unsafe fn #import_name(#(#abi_arguments),*) -> #abi_ret {
- #(
- drop(#abi_argument_names);
- )*
- panic!("cannot call wasm-bindgen imported functions on \
- non-wasm targets");
- }
- },
+ extern_fn(
+ import_name,
+ attrs,
+ abi_arguments,
+ abi_argument_names,
+ abi_ret,
+ ),
&self.rust_name,
);
+ let maybe_unsafe = if self.function.r#unsafe {
+ Some(quote! {unsafe})
+ } else {
+ None
+ };
let maybe_async = if self.function.r#async {
Some(quote! {async})
} else {
@@ -1131,8 +1501,8 @@ impl TryToTokens for ast::ImportFunction {
#[allow(nonstandard_style)]
#[allow(clippy::all, clippy::nursery, clippy::pedantic, clippy::restriction)]
#(#attrs)*
- #[doc = #doc_comment]
- #vis #maybe_async fn #rust_name(#me #(#arguments),*) #ret {
+ #doc
+ #vis #maybe_async #maybe_unsafe fn #rust_name(#me #(#arguments),*) #ret {
#extern_fn
unsafe {
@@ -1165,17 +1535,19 @@ impl TryToTokens for ast::ImportFunction {
// See comment above in ast::Export for what's going on here.
struct DescribeImport<'a> {
kind: &'a ast::ImportKind,
+ wasm_bindgen: &'a syn::Path,
}
-impl<'a> ToTokens for DescribeImport<'a> {
+impl ToTokens for DescribeImport<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let f = match *self.kind {
ast::ImportKind::Function(ref f) => f,
ast::ImportKind::Static(_) => return,
+ ast::ImportKind::String(_) => return,
ast::ImportKind::Type(_) => return,
ast::ImportKind::Enum(_) => return,
};
- let argtys = f.function.arguments.iter().map(|arg| &arg.ty);
+ let argtys = f.function.arguments.iter().map(|arg| &arg.pat_type.ty);
let nargs = f.function.arguments.len() as u32;
let inform_ret = match &f.js_ret {
Some(ref t) => quote! { <#t as WasmDescribe>::describe(); },
@@ -1195,6 +1567,7 @@ impl<'a> ToTokens for DescribeImport<'a> {
#inform_ret
},
attrs: f.function.rust_attrs.clone(),
+ wasm_bindgen: self.wasm_bindgen,
}
.to_tokens(tokens);
}
@@ -1203,58 +1576,140 @@ impl<'a> ToTokens for DescribeImport<'a> {
impl ToTokens for ast::Enum {
fn to_tokens(&self, into: &mut TokenStream) {
let enum_name = &self.rust_name;
+ let name_str = self.js_name.to_string();
+ let name_len = name_str.len() as u32;
+ let name_chars = name_str.chars().map(|c| c as u32);
let hole = &self.hole;
+ let underlying = if self.signed {
+ quote! { i32 }
+ } else {
+ quote! { u32 }
+ };
let cast_clauses = self.variants.iter().map(|variant| {
let variant_name = &variant.name;
quote! {
- if js == #enum_name::#variant_name as u32 {
+ if js == #enum_name::#variant_name as #underlying {
#enum_name::#variant_name
}
}
});
+ let try_from_cast_clauses = cast_clauses.clone();
+ let wasm_bindgen = &self.wasm_bindgen;
(quote! {
#[automatically_derived]
- impl wasm_bindgen::convert::IntoWasmAbi for #enum_name {
- type Abi = u32;
+ impl #wasm_bindgen::convert::IntoWasmAbi for #enum_name {
+ type Abi = #underlying;
#[inline]
- fn into_abi(self) -> u32 {
- self as u32
+ fn into_abi(self) -> #underlying {
+ self as #underlying
}
}
#[automatically_derived]
- impl wasm_bindgen::convert::FromWasmAbi for #enum_name {
- type Abi = u32;
+ impl #wasm_bindgen::convert::FromWasmAbi for #enum_name {
+ type Abi = #underlying;
#[inline]
- unsafe fn from_abi(js: u32) -> Self {
+ unsafe fn from_abi(js: #underlying) -> Self {
#(#cast_clauses else)* {
- wasm_bindgen::throw_str("invalid enum value passed")
+ #wasm_bindgen::throw_str("invalid enum value passed")
}
}
}
#[automatically_derived]
- impl wasm_bindgen::convert::OptionFromWasmAbi for #enum_name {
+ impl #wasm_bindgen::convert::OptionFromWasmAbi for #enum_name {
#[inline]
- fn is_none(val: &u32) -> bool { *val == #hole }
+ fn is_none(val: &Self::Abi) -> bool { *val == #hole as #underlying }
}
#[automatically_derived]
- impl wasm_bindgen::convert::OptionIntoWasmAbi for #enum_name {
+ impl #wasm_bindgen::convert::OptionIntoWasmAbi for #enum_name {
#[inline]
- fn none() -> Self::Abi { #hole }
+ fn none() -> Self::Abi { #hole as #underlying }
}
#[automatically_derived]
- impl wasm_bindgen::describe::WasmDescribe for #enum_name {
+ impl #wasm_bindgen::describe::WasmDescribe for #enum_name {
fn describe() {
- use wasm_bindgen::describe::*;
+ use #wasm_bindgen::describe::*;
inform(ENUM);
+ inform(#name_len);
+ #(inform(#name_chars);)*
inform(#hole);
}
}
+
+ #[automatically_derived]
+ impl #wasm_bindgen::__rt::core::convert::From<#enum_name> for
+ #wasm_bindgen::JsValue
+ {
+ fn from(value: #enum_name) -> Self {
+ #wasm_bindgen::JsValue::from_f64((value as #underlying).into())
+ }
+ }
+
+ #[automatically_derived]
+ impl #wasm_bindgen::convert::TryFromJsValue for #enum_name {
+ type Error = #wasm_bindgen::JsValue;
+
+ fn try_from_js_value(value: #wasm_bindgen::JsValue)
+ -> #wasm_bindgen::__rt::core::result::Result::Error> {
+ use #wasm_bindgen::__rt::core::convert::TryFrom;
+ let js = f64::try_from(&value)? as #underlying;
+
+ #wasm_bindgen::__rt::core::result::Result::Ok(
+ #(#try_from_cast_clauses else)* {
+ return #wasm_bindgen::__rt::core::result::Result::Err(value)
+ }
+ )
+ }
+ }
+
+ #[automatically_derived]
+ impl #wasm_bindgen::describe::WasmDescribeVector for #enum_name {
+ fn describe_vector() {
+ use #wasm_bindgen::describe::*;
+ inform(VECTOR);
+ <#wasm_bindgen::JsValue as #wasm_bindgen::describe::WasmDescribe>::describe();
+ }
+ }
+
+ #[automatically_derived]
+ impl #wasm_bindgen::convert::VectorIntoWasmAbi for #enum_name {
+ type Abi = <
+ #wasm_bindgen::__rt::alloc::boxed::Box<[#wasm_bindgen::JsValue]>
+ as #wasm_bindgen::convert::IntoWasmAbi
+ >::Abi;
+
+ fn vector_into_abi(
+ vector: #wasm_bindgen::__rt::alloc::boxed::Box<[#enum_name]>
+ ) -> Self::Abi {
+ #wasm_bindgen::convert::js_value_vector_into_abi(vector)
+ }
+ }
+
+ #[automatically_derived]
+ impl #wasm_bindgen::convert::VectorFromWasmAbi for #enum_name {
+ type Abi = <
+ #wasm_bindgen::__rt::alloc::boxed::Box<[#wasm_bindgen::JsValue]>
+ as #wasm_bindgen::convert::FromWasmAbi
+ >::Abi;
+
+ unsafe fn vector_from_abi(
+ js: Self::Abi
+ ) -> #wasm_bindgen::__rt::alloc::boxed::Box<[#enum_name]> {
+ #wasm_bindgen::convert::js_value_vector_from_abi(js)
+ }
+ }
+
+ #[automatically_derived]
+ impl #wasm_bindgen::__rt::VectorIntoJsValue for #enum_name {
+ fn vector_into_jsvalue(vector: #wasm_bindgen::__rt::alloc::boxed::Box<[#enum_name]>) -> #wasm_bindgen::JsValue {
+ #wasm_bindgen::__rt::js_value_vector_into_jsvalue(vector)
+ }
+ }
})
.to_tokens(into);
}
@@ -1262,56 +1717,141 @@ impl ToTokens for ast::Enum {
impl ToTokens for ast::ImportStatic {
fn to_tokens(&self, into: &mut TokenStream) {
- let name = &self.rust_name;
let ty = &self.ty;
- let shim_name = &self.shim;
- let vis = &self.vis;
- (quote! {
- #[automatically_derived]
- #vis static #name: wasm_bindgen::JsStatic<#ty> = {
- fn init() -> #ty {
- #[link(wasm_import_module = "__wbindgen_placeholder__")]
- #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
- extern "C" {
- fn #shim_name() -> <#ty as wasm_bindgen::convert::FromWasmAbi>::Abi;
- }
- #[cfg(not(all(target_arch = "wasm32", not(target_os = "emscripten"))))]
- unsafe fn #shim_name() -> <#ty as wasm_bindgen::convert::FromWasmAbi>::Abi {
- panic!("cannot access imported statics on non-wasm targets")
- }
- unsafe {
- <#ty as wasm_bindgen::convert::FromWasmAbi>::from_abi(#shim_name())
- }
- }
- thread_local!(static _VAL: #ty = init(););
- wasm_bindgen::JsStatic {
- __inner: &_VAL,
- }
- };
- })
- .to_tokens(into);
+ if let Some(thread_local) = self.thread_local {
+ thread_local_import(
+ &self.vis,
+ &self.rust_name,
+ &self.wasm_bindgen,
+ ty,
+ ty,
+ &self.shim,
+ thread_local,
+ )
+ .to_tokens(into)
+ } else {
+ let vis = &self.vis;
+ let name = &self.rust_name;
+ let wasm_bindgen = &self.wasm_bindgen;
+ let ty = &self.ty;
+ let shim_name = &self.shim;
+ let init = static_init(wasm_bindgen, ty, shim_name);
+
+ into.extend(quote! {
+ #[automatically_derived]
+ #[deprecated = "use with `#[wasm_bindgen(thread_local_v2)]` instead"]
+ });
+ into.extend(
+ quote_spanned! { name.span() => #vis static #name: #wasm_bindgen::JsStatic<#ty> = {
+ fn init() -> #ty {
+ #init
+ }
+ #wasm_bindgen::__rt::std::thread_local!(static _VAL: #ty = init(););
+ #wasm_bindgen::JsStatic {
+ __inner: &_VAL,
+ }
+ };
+ },
+ );
+ }
Descriptor {
- ident: &shim_name,
+ ident: &self.shim,
inner: quote! {
<#ty as WasmDescribe>::describe();
},
attrs: vec![],
+ wasm_bindgen: &self.wasm_bindgen,
}
.to_tokens(into);
}
}
+impl ToTokens for ast::ImportString {
+ fn to_tokens(&self, into: &mut TokenStream) {
+ let js_sys = &self.js_sys;
+ let actual_ty: syn::Type = parse_quote!(#js_sys::JsString);
+
+ thread_local_import(
+ &self.vis,
+ &self.rust_name,
+ &self.wasm_bindgen,
+ &actual_ty,
+ &self.ty,
+ &self.shim,
+ self.thread_local,
+ )
+ .to_tokens(into);
+ }
+}
+
+fn thread_local_import(
+ vis: &syn::Visibility,
+ name: &Ident,
+ wasm_bindgen: &syn::Path,
+ actual_ty: &syn::Type,
+ ty: &syn::Type,
+ shim_name: &Ident,
+ thread_local: ast::ThreadLocal,
+) -> TokenStream {
+ let init = static_init(wasm_bindgen, ty, shim_name);
+
+ match thread_local {
+ ast::ThreadLocal::V1 => quote! {
+ #wasm_bindgen::__rt::std::thread_local! {
+ #[automatically_derived]
+ #[deprecated = "use with `#[wasm_bindgen(thread_local_v2)]` instead"]
+ #vis static #name: #actual_ty = {
+ #init
+ };
+ }
+ },
+ ast::ThreadLocal::V2 => {
+ quote! {
+ #vis static #name: #wasm_bindgen::JsThreadLocal<#actual_ty> = {
+ fn init() -> #actual_ty {
+ #init
+ }
+ #wasm_bindgen::__wbindgen_thread_local!(#wasm_bindgen, #actual_ty)
+ };
+ }
+ }
+ }
+}
+
+fn static_init(wasm_bindgen: &syn::Path, ty: &syn::Type, shim_name: &Ident) -> TokenStream {
+ let abi_ret = quote! {
+ #wasm_bindgen::convert::WasmRet<<#ty as #wasm_bindgen::convert::FromWasmAbi>::Abi>
+ };
+ quote! {
+ #[link(wasm_import_module = "__wbindgen_placeholder__")]
+ #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))]
+ extern "C" {
+ fn #shim_name() -> #abi_ret;
+ }
+
+ #[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))))]
+ unsafe fn #shim_name() -> #abi_ret {
+ panic!("cannot access imported statics on non-wasm targets")
+ }
+
+ unsafe {
+ <#ty as #wasm_bindgen::convert::FromWasmAbi>::from_abi(#shim_name().join())
+ }
+ }
+}
+
/// Emits the necessary glue tokens for "descriptor", generating an appropriate
/// symbol name as well as attributes around the descriptor function itself.
struct Descriptor<'a, T> {
ident: &'a Ident,
inner: T,
attrs: Vec,
+ wasm_bindgen: &'a syn::Path,
}
-impl<'a, T: ToTokens> ToTokens for Descriptor<'a, T> {
+impl ToTokens for Descriptor<'_, T> {
fn to_tokens(&self, tokens: &mut TokenStream) {
// It's possible for the same descriptor to be emitted in two different
// modules (aka a value imported twice in a crate, each in a separate
@@ -1321,46 +1861,93 @@ impl<'a, T: ToTokens> ToTokens for Descriptor<'a, T> {
// It's up to the descriptors themselves to ensure they have unique
// names for unique items imported, currently done via `ShortHash` and
// hashing appropriate data into the symbol name.
- lazy_static::lazy_static! {
- static ref DESCRIPTORS_EMITTED: Mutex> = Default::default();
+ thread_local! {
+ static DESCRIPTORS_EMITTED: RefCell> = RefCell::default();
}
let ident = self.ident;
- if !DESCRIPTORS_EMITTED
- .lock()
- .unwrap()
- .insert(ident.to_string())
- {
+ if !DESCRIPTORS_EMITTED.with(|list| list.borrow_mut().insert(ident.to_string())) {
return;
}
let name = Ident::new(&format!("__wbindgen_describe_{}", ident), ident.span());
- let const_name = Ident::new(
- &format!("__wbindgen_const_describe_{}", ident),
- ident.span(),
- );
let inner = &self.inner;
let attrs = &self.attrs;
+ let wasm_bindgen = &self.wasm_bindgen;
(quote! {
- #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
+ #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))]
#[automatically_derived]
- const #const_name: () = {
+ const _: () = {
+ #wasm_bindgen::__wbindgen_coverage! {
#(#attrs)*
#[no_mangle]
#[doc(hidden)]
pub extern "C" fn #name() {
- use wasm_bindgen::describe::*;
+ use #wasm_bindgen::describe::*;
// See definition of `link_mem_intrinsics` for what this is doing
- wasm_bindgen::__rt::link_mem_intrinsics();
+ #wasm_bindgen::__rt::link_mem_intrinsics();
#inner
}
+ }
};
})
.to_tokens(tokens);
}
}
+fn extern_fn(
+ import_name: &Ident,
+ attrs: &[syn::Attribute],
+ abi_arguments: &[TokenStream],
+ abi_argument_names: &[Ident],
+ abi_ret: TokenStream,
+) -> TokenStream {
+ quote! {
+ #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))]
+ #(#attrs)*
+ #[link(wasm_import_module = "__wbindgen_placeholder__")]
+ extern "C" {
+ fn #import_name(#(#abi_arguments),*) -> #abi_ret;
+ }
+
+ #[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))))]
+ unsafe fn #import_name(#(#abi_arguments),*) -> #abi_ret {
+ #(
+ drop(#abi_argument_names);
+ )*
+ panic!("cannot call wasm-bindgen imported functions on \
+ non-wasm targets");
+ }
+ }
+}
+
+/// Splats an argument with the given name and ABI type into 4 arguments, one
+/// for each primitive that the ABI type splits into.
+///
+/// Returns an `(args, names)` pair, where `args` is the list of arguments to
+/// be inserted into the function signature, and `names` is a list of the names
+/// of those arguments.
+fn splat(
+ wasm_bindgen: &syn::Path,
+ name: &Ident,
+ abi: &TokenStream,
+) -> (Vec, Vec) {
+ let mut args = Vec::new();
+ let mut names = Vec::new();
+
+ for n in 1_u32..=4 {
+ let arg_name = format_ident!("{}_{}", name, n);
+ let prim_name = format_ident!("Prim{}", n);
+ args.push(quote! {
+ #arg_name: <#abi as #wasm_bindgen::convert::WasmAbi>::#prim_name
+ });
+ names.push(arg_name);
+ }
+
+ (args, names)
+}
+
/// Converts `span` into a stream of tokens, and attempts to ensure that `input`
/// has all the appropriate span information so errors in it point to `span`.
fn respan(input: TokenStream, span: &dyn ToTokens) -> TokenStream {
@@ -1371,9 +1958,9 @@ fn respan(input: TokenStream, span: &dyn ToTokens) -> TokenStream {
for (i, token) in spans.into_iter().enumerate() {
if i == 0 {
- first_span = token.span();
+ first_span = Span::call_site().located_at(token.span());
}
- last_span = token.span();
+ last_span = Span::call_site().located_at(token.span());
}
let mut new_tokens = Vec::new();
diff --git a/crates/backend/src/encode.rs b/crates/backend/src/encode.rs
index 6fa1245b32c..1285f19fc93 100644
--- a/crates/backend/src/encode.rs
+++ b/crates/backend/src/encode.rs
@@ -5,12 +5,20 @@ use std::collections::HashMap;
use std::env;
use std::fs;
use std::path::PathBuf;
+use syn::ext::IdentExt;
use crate::ast;
use crate::Diagnostic;
+#[derive(Clone)]
+pub enum EncodeChunk {
+ EncodedBuf(Vec),
+ StrExpr(syn::Expr),
+ // TODO: support more expr type;
+}
+
pub struct EncodeResult {
- pub custom_section: Vec,
+ pub custom_section: Vec,
pub included_files: Vec,
}
@@ -44,6 +52,7 @@ struct LocalFile {
path: PathBuf,
definition: Span,
new_identifier: String,
+ linked_module: bool,
}
impl Interner {
@@ -77,19 +86,24 @@ impl Interner {
///
/// Note that repeated invocations of this function will be memoized, so the
/// same `id` will always return the same resulting unique `id`.
- fn resolve_import_module(&self, id: &str, span: Span) -> Result<&str, Diagnostic> {
+ fn resolve_import_module(
+ &self,
+ id: &str,
+ span: Span,
+ linked_module: bool,
+ ) -> Result {
let mut files = self.files.borrow_mut();
if let Some(file) = files.get(id) {
- return Ok(self.intern_str(&file.new_identifier));
+ return Ok(ImportModule::Named(self.intern_str(&file.new_identifier)));
}
self.check_for_package_json();
- let path = if id.starts_with("/") {
- self.root.join(&id[1..])
+ let path = if let Some(id) = id.strip_prefix('/') {
+ self.root.join(id)
} else if id.starts_with("./") || id.starts_with("../") {
let msg = "relative module paths aren't supported yet";
return Err(Diagnostic::span_error(span, msg));
} else {
- return Ok(self.intern_str(&id));
+ return Ok(ImportModule::RawNamed(self.intern_str(id)));
};
// Generate a unique ID which is somewhat readable as well, so mix in
@@ -99,10 +113,11 @@ impl Interner {
path,
definition: span,
new_identifier,
+ linked_module,
};
files.insert(id.to_string(), file);
drop(files);
- self.resolve_import_module(id, span)
+ self.resolve_import_module(id, span, linked_module)
}
fn unique_crate_identifier(&self) -> String {
@@ -144,8 +159,14 @@ fn shared_program<'a>(
typescript_custom_sections: prog
.typescript_custom_sections
.iter()
- .map(|x| -> &'a str { &x })
+ .map(|x| shared_lit_or_expr(x, intern))
.collect(),
+ linked_modules: prog
+ .linked_modules
+ .iter()
+ .enumerate()
+ .map(|(i, a)| shared_linked_module(&prog.link_function_name(i), a, intern))
+ .collect::, _>>()?,
local_modules: intern
.files
.borrow()
@@ -155,6 +176,7 @@ fn shared_program<'a>(
.map(|s| LocalModule {
identifier: intern.intern_str(&file.new_identifier),
contents: intern.intern_str(&s),
+ linked_module: file.linked_module,
})
.map_err(|e| {
let msg = format!("failed to read file `{}`: {}", file.path.display(), e);
@@ -180,13 +202,10 @@ fn shared_export<'a>(
export: &'a ast::Export,
intern: &'a Interner,
) -> Result, Diagnostic> {
- let consumed = match export.method_self {
- Some(ast::MethodSelf::ByValue) => true,
- _ => false,
- };
+ let consumed = matches!(export.method_self, Some(ast::MethodSelf::ByValue));
let method_kind = from_ast_method_kind(&export.function, intern, &export.method_kind)?;
Ok(Export {
- class: export.js_class.as_ref().map(|s| &**s),
+ class: export.js_class.as_deref(),
comments: export.comments.iter().map(|s| &**s).collect(),
consumed,
function: shared_function(&export.function, intern),
@@ -196,28 +215,41 @@ fn shared_export<'a>(
}
fn shared_function<'a>(func: &'a ast::Function, _intern: &'a Interner) -> Function<'a> {
- let arg_names = func
- .arguments
- .iter()
- .enumerate()
- .map(|(idx, arg)| {
- if let syn::Pat::Ident(x) = &*arg.pat {
- return x.ident.to_string();
- }
- format!("arg{}", idx)
- })
- .collect::>();
+ let args =
+ func.arguments
+ .iter()
+ .enumerate()
+ .map(|(idx, arg)| FunctionArgumentData {
+ // use argument's "js_name" if it was provided via attributes
+ // if not use the original Rust argument ident
+ name: arg.js_name.clone().unwrap_or(
+ if let syn::Pat::Ident(x) = &*arg.pat_type.pat {
+ x.ident.unraw().to_string()
+ } else {
+ format!("arg{}", idx)
+ },
+ ),
+ ty_override: arg.js_type.as_deref(),
+ desc: arg.desc.as_deref(),
+ })
+ .collect::>();
+
Function {
- arg_names,
+ args,
asyncness: func.r#async,
name: &func.name,
generate_typescript: func.generate_typescript,
+ generate_jsdoc: func.generate_jsdoc,
+ variadic: func.variadic,
+ ret_ty_override: func.ret.as_ref().and_then(|v| v.js_type.as_deref()),
+ ret_desc: func.ret.as_ref().and_then(|v| v.desc.as_deref()),
}
}
fn shared_enum<'a>(e: &'a ast::Enum, intern: &'a Interner) -> Enum<'a> {
Enum {
name: &e.js_name,
+ signed: e.signed,
variants: e
.variants
.iter()
@@ -238,19 +270,48 @@ fn shared_variant<'a>(v: &'a ast::Variant, intern: &'a Interner) -> EnumVariant<
fn shared_import<'a>(i: &'a ast::Import, intern: &'a Interner) -> Result, Diagnostic> {
Ok(Import {
- module: match &i.module {
- ast::ImportModule::Named(m, span) => {
- ImportModule::Named(intern.resolve_import_module(m, *span)?)
- }
- ast::ImportModule::RawNamed(m, _span) => ImportModule::RawNamed(intern.intern_str(m)),
- ast::ImportModule::Inline(idx, _) => ImportModule::Inline(*idx as u32),
- ast::ImportModule::None => ImportModule::None,
- },
+ module: i
+ .module
+ .as_ref()
+ .map(|m| shared_module(m, intern, false))
+ .transpose()?,
js_namespace: i.js_namespace.clone(),
kind: shared_import_kind(&i.kind, intern)?,
})
}
+fn shared_lit_or_expr<'a>(i: &'a ast::LitOrExpr, _intern: &'a Interner) -> LitOrExpr<'a> {
+ match i {
+ ast::LitOrExpr::Lit(lit) => LitOrExpr::Lit(lit),
+ ast::LitOrExpr::Expr(expr) => LitOrExpr::Expr(expr),
+ }
+}
+
+fn shared_linked_module<'a>(
+ name: &str,
+ i: &'a ast::ImportModule,
+ intern: &'a Interner,
+) -> Result, Diagnostic> {
+ Ok(LinkedModule {
+ module: shared_module(i, intern, true)?,
+ link_function_name: intern.intern_str(name),
+ })
+}
+
+fn shared_module<'a>(
+ m: &'a ast::ImportModule,
+ intern: &'a Interner,
+ linked_module: bool,
+) -> Result, Diagnostic> {
+ Ok(match m {
+ ast::ImportModule::Named(m, span) => {
+ intern.resolve_import_module(m, *span, linked_module)?
+ }
+ ast::ImportModule::RawNamed(m, _span) => ImportModule::RawNamed(intern.intern_str(m)),
+ ast::ImportModule::Inline(idx, _) => ImportModule::Inline(*idx as u32),
+ })
+}
+
fn shared_import_kind<'a>(
i: &'a ast::ImportKind,
intern: &'a Interner,
@@ -258,6 +319,7 @@ fn shared_import_kind<'a>(
Ok(match i {
ast::ImportKind::Function(f) => ImportKind::Function(shared_import_function(f, intern)?),
ast::ImportKind::Static(f) => ImportKind::Static(shared_import_static(f, intern)),
+ ast::ImportKind::String(f) => ImportKind::String(shared_import_string(f, intern)),
ast::ImportKind::Type(f) => ImportKind::Type(shared_import_type(f, intern)),
ast::ImportKind::Enum(f) => ImportKind::Enum(shared_import_enum(f, intern)),
})
@@ -293,6 +355,13 @@ fn shared_import_static<'a>(i: &'a ast::ImportStatic, intern: &'a Interner) -> I
}
}
+fn shared_import_string<'a>(i: &'a ast::ImportString, intern: &'a Interner) -> ImportString<'a> {
+ ImportString {
+ shim: intern.intern(&i.shim),
+ string: &i.string,
+ }
+}
+
fn shared_import_type<'a>(i: &'a ast::ImportType, intern: &'a Interner) -> ImportType<'a> {
ImportType {
name: &i.js_name,
@@ -301,8 +370,13 @@ fn shared_import_type<'a>(i: &'a ast::ImportType, intern: &'a Interner) -> Impor
}
}
-fn shared_import_enum<'a>(_i: &'a ast::ImportEnum, _intern: &'a Interner) -> ImportEnum {
- ImportEnum {}
+fn shared_import_enum<'a>(i: &'a ast::StringEnum, _intern: &'a Interner) -> StringEnum<'a> {
+ StringEnum {
+ name: &i.js_name,
+ generate_typescript: i.generate_typescript,
+ variant_values: i.variant_values.iter().map(|x| &**x).collect(),
+ comments: i.comments.iter().map(|s| &**s).collect(),
+ }
}
fn shared_struct<'a>(s: &'a ast::Struct, intern: &'a Interner) -> Struct<'a> {
@@ -325,6 +399,7 @@ fn shared_struct_field<'a>(s: &'a ast::StructField, _intern: &'a Interner) -> St
readonly: s.readonly,
comments: s.comments.iter().map(|s| &**s).collect(),
generate_typescript: s.generate_typescript,
+ generate_jsdoc: s.generate_jsdoc,
}
}
@@ -333,27 +408,48 @@ trait Encode {
}
struct Encoder {
- dst: Vec,
+ dst: Vec,
+}
+
+enum LitOrExpr<'a> {
+ Expr(&'a syn::Expr),
+ Lit(&'a str),
+}
+
+impl Encode for LitOrExpr<'_> {
+ fn encode(&self, dst: &mut Encoder) {
+ match self {
+ LitOrExpr::Expr(expr) => {
+ dst.dst.push(EncodeChunk::StrExpr((*expr).clone()));
+ }
+ LitOrExpr::Lit(s) => s.encode(dst),
+ }
+ }
}
impl Encoder {
fn new() -> Encoder {
- Encoder {
- dst: vec![0, 0, 0, 0],
- }
+ Encoder { dst: vec![] }
}
- fn finish(mut self) -> Vec {
- let len = self.dst.len() - 4;
- self.dst[0] = (len >> 0) as u8;
- self.dst[1] = (len >> 8) as u8;
- self.dst[2] = (len >> 16) as u8;
- self.dst[3] = (len >> 24) as u8;
+ fn finish(self) -> Vec {
self.dst
}
fn byte(&mut self, byte: u8) {
- self.dst.push(byte);
+ if let Some(EncodeChunk::EncodedBuf(buf)) = self.dst.last_mut() {
+ buf.push(byte);
+ } else {
+ self.dst.push(EncodeChunk::EncodedBuf(vec![byte]));
+ }
+ }
+
+ fn extend_from_slice(&mut self, slice: &[u8]) {
+ if let Some(EncodeChunk::EncodedBuf(buf)) = self.dst.last_mut() {
+ buf.extend_from_slice(slice);
+ } else {
+ self.dst.push(EncodeChunk::EncodedBuf(slice.to_owned()));
+ }
}
}
@@ -377,25 +473,25 @@ impl Encode for u32 {
impl Encode for usize {
fn encode(&self, dst: &mut Encoder) {
- assert!(*self <= u32::max_value() as usize);
+ assert!(*self <= u32::MAX as usize);
(*self as u32).encode(dst);
}
}
-impl<'a> Encode for &'a [u8] {
+impl Encode for &[u8] {
fn encode(&self, dst: &mut Encoder) {
self.len().encode(dst);
- dst.dst.extend_from_slice(*self);
+ dst.extend_from_slice(self);
}
}
-impl<'a> Encode for &'a str {
+impl Encode for &str {
fn encode(&self, dst: &mut Encoder) {
self.as_bytes().encode(dst);
}
}
-impl<'a> Encode for String {
+impl Encode for String {
fn encode(&self, dst: &mut Encoder) {
self.as_bytes().encode(dst);
}
@@ -509,12 +605,12 @@ fn from_ast_method_kind<'a>(
let is_static = *is_static;
let kind = match kind {
ast::OperationKind::Getter(g) => {
- let g = g.as_ref().map(|g| intern.intern(g));
+ let g = g.as_ref().map(|g| intern.intern_str(g));
OperationKind::Getter(g.unwrap_or_else(|| function.infer_getter_property()))
}
ast::OperationKind::Regular => OperationKind::Regular,
ast::OperationKind::Setter(s) => {
- let s = s.as_ref().map(|s| intern.intern(s));
+ let s = s.as_ref().map(|s| intern.intern_str(s));
OperationKind::Setter(match s {
Some(s) => s,
None => intern.intern_str(&function.infer_setter_property()?),
diff --git a/crates/backend/src/error.rs b/crates/backend/src/error.rs
index 3e65cd745cc..19d991b3ce4 100644
--- a/crates/backend/src/error.rs
+++ b/crates/backend/src/error.rs
@@ -70,7 +70,7 @@ impl Diagnostic {
/// Attempt to generate a `Diagnostic` from a vector of other `Diagnostic` instances.
/// If the `Vec` is empty, returns `Ok(())`, otherwise returns the new `Diagnostic`
pub fn from_vec(diagnostics: Vec) -> Result<(), Diagnostic> {
- if diagnostics.len() == 0 {
+ if diagnostics.is_empty() {
Ok(())
} else {
Err(Diagnostic {
diff --git a/crates/backend/src/util.rs b/crates/backend/src/util.rs
index 73cf20bb0ea..8424e602a08 100644
--- a/crates/backend/src/util.rs
+++ b/crates/backend/src/util.rs
@@ -12,25 +12,24 @@ use std::sync::atomic::Ordering::SeqCst;
use crate::ast;
use proc_macro2::{self, Ident};
-use syn;
/// Check whether a given `&str` is a Rust keyword
+#[rustfmt::skip]
fn is_rust_keyword(name: &str) -> bool {
- match name {
+ matches!(name,
"abstract" | "alignof" | "as" | "become" | "box" | "break" | "const" | "continue"
| "crate" | "do" | "else" | "enum" | "extern" | "false" | "final" | "fn" | "for" | "if"
| "impl" | "in" | "let" | "loop" | "macro" | "match" | "mod" | "move" | "mut"
| "offsetof" | "override" | "priv" | "proc" | "pub" | "pure" | "ref" | "return"
| "Self" | "self" | "sizeof" | "static" | "struct" | "super" | "trait" | "true"
| "type" | "typeof" | "unsafe" | "unsized" | "use" | "virtual" | "where" | "while"
- | "yield" | "bool" | "_" => true,
- _ => false,
- }
+ | "yield" | "bool" | "_"
+ )
}
/// Create an `Ident`, possibly mangling it if it conflicts with a Rust keyword.
pub fn rust_ident(name: &str) -> Ident {
- if name == "" {
+ if name.is_empty() {
panic!("tried to create empty Ident (from \"\")");
} else if is_rust_keyword(name) {
Ident::new(&format!("{}_", name), proc_macro2::Span::call_site())
@@ -120,7 +119,7 @@ pub fn ident_ty(ident: Ident) -> syn::Type {
/// Convert an ImportFunction into the more generic Import type, wrapping the provided function
pub fn wrap_import_function(function: ast::ImportFunction) -> ast::Import {
ast::Import {
- module: ast::ImportModule::None,
+ module: None,
js_namespace: None,
kind: ast::ImportKind::Function(function),
}
diff --git a/crates/cli-support/Cargo.toml b/crates/cli-support/Cargo.toml
index 4a1e3dbb45e..bb4ba82842e 100644
--- a/crates/cli-support/Cargo.toml
+++ b/crates/cli-support/Cargo.toml
@@ -1,30 +1,33 @@
[package]
-name = "wasm-bindgen-cli-support"
-version = "0.2.80"
authors = ["The wasm-bindgen Developers"]
-license = "MIT/Apache-2.0"
-repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/cli-support"
-homepage = "https://rustwasm.github.io/wasm-bindgen/"
-documentation = "https://docs.rs/wasm-bindgen-cli-support"
description = """
Shared support for the wasm-bindgen-cli package, an internal dependency
"""
-edition = '2018'
+documentation = "https://docs.rs/wasm-bindgen-cli-support"
+edition = "2021"
+homepage = "https://rustwasm.github.io/wasm-bindgen/"
+include = ["/LICENSE-*", "/src"]
+license = "MIT OR Apache-2.0"
+name = "wasm-bindgen-cli-support"
+repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/cli-support"
+rust-version = "1.76"
+version = "0.2.100"
[dependencies]
anyhow = "1.0"
-base64 = "0.9"
+base64 = "0.22"
log = "0.4"
rustc-demangle = "0.1.13"
+serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tempfile = "3.0"
-walrus = "0.19.0"
-wasm-bindgen-externref-xform = { path = '../externref-xform', version = '=0.2.80' }
-wasm-bindgen-multi-value-xform = { path = '../multi-value-xform', version = '=0.2.80' }
-wasm-bindgen-shared = { path = "../shared", version = '=0.2.80' }
-wasm-bindgen-threads-xform = { path = '../threads-xform', version = '=0.2.80' }
-wasm-bindgen-wasm-conventions = { path = '../wasm-conventions', version = '=0.2.80' }
-wasm-bindgen-wasm-interpreter = { path = "../wasm-interpreter", version = '=0.2.80' }
-wit-text = "0.8.0"
-wit-walrus = "0.6.0"
-wit-validator = "0.2.0"
+walrus = { version = "0.23", features = ['parallel'] }
+wasm-bindgen-externref-xform = { path = '../externref-xform', version = '=0.2.100' }
+wasm-bindgen-multi-value-xform = { path = '../multi-value-xform', version = '=0.2.100' }
+wasm-bindgen-shared = { path = "../shared", version = '=0.2.100' }
+wasm-bindgen-threads-xform = { path = '../threads-xform', version = '=0.2.100' }
+wasm-bindgen-wasm-conventions = { path = '../wasm-conventions', version = '=0.2.100' }
+wasm-bindgen-wasm-interpreter = { path = "../wasm-interpreter", version = '=0.2.100' }
+
+[lints]
+workspace = true
diff --git a/crates/cli-support/src/decode.rs b/crates/cli-support/src/decode.rs
index d58b49ff06d..f527acea2d2 100644
--- a/crates/cli-support/src/decode.rs
+++ b/crates/cli-support/src/decode.rs
@@ -1,19 +1,37 @@
-use std::str;
+use std::{ops::Deref, str};
pub trait Decode<'src>: Sized {
fn decode(data: &mut &'src [u8]) -> Self;
fn decode_all(mut data: &'src [u8]) -> Self {
let ret = Self::decode(&mut data);
- assert!(data.len() == 0);
- return ret;
+ assert!(data.is_empty());
+ ret
}
}
-fn get<'a>(b: &mut &'a [u8]) -> u8 {
+pub struct LitOrExpr<'src> {
+ str: &'src str,
+}
+
+fn get(b: &mut &[u8]) -> u8 {
let r = b[0];
*b = &b[1..];
- return r;
+ r
+}
+
+impl Deref for LitOrExpr<'_> {
+ type Target = str;
+ fn deref(&self) -> &Self::Target {
+ self.str
+ }
+}
+
+impl<'src> Decode<'src> for LitOrExpr<'src> {
+ fn decode(data: &mut &'src [u8]) -> Self {
+ let str = <&'src str>::decode(data);
+ Self { str }
+ }
}
impl<'src> Decode<'src> for bool {
diff --git a/crates/cli-support/src/descriptor.rs b/crates/cli-support/src/descriptor.rs
index a3b73a2221a..3ddf75405b3 100644
--- a/crates/cli-support/src/descriptor.rs
+++ b/crates/cli-support/src/descriptor.rs
@@ -1,5 +1,7 @@
use std::char;
+use wasm_bindgen_shared::identifier::is_valid_ident;
+
macro_rules! tys {
($($a:ident)*) => (tys! { @ ($($a)*) 0 });
(@ () $v:expr) => {};
@@ -19,6 +21,8 @@ tys! {
U32
I64
U64
+ I128
+ U128
F32
F64
BOOLEAN
@@ -28,17 +32,20 @@ tys! {
STRING
REF
REFMUT
+ LONGREF
SLICE
VECTOR
EXTERNREF
NAMED_EXTERNREF
ENUM
+ STRING_ENUM
RUST_STRUCT
CHAR
OPTIONAL
RESULT
UNIT
CLAMPED
+ NONNULL
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -52,6 +59,8 @@ pub enum Descriptor {
U32,
I64,
U64,
+ I128,
+ U128,
F32,
F64,
Boolean,
@@ -65,12 +74,21 @@ pub enum Descriptor {
String,
Externref,
NamedExternref(String),
- Enum { hole: u32 },
+ Enum {
+ name: String,
+ hole: u32,
+ },
+ StringEnum {
+ name: String,
+ invalid: u32,
+ hole: u32,
+ },
RustStruct(String),
Char,
Option(Box),
Result(Box),
Unit,
+ NonNull,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -89,7 +107,7 @@ pub struct Closure {
pub mutable: bool,
}
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum VectorKind {
I8,
U8,
@@ -120,11 +138,13 @@ impl Descriptor {
I16 => Descriptor::I16,
I32 => Descriptor::I32,
I64 => Descriptor::I64,
+ I128 => Descriptor::I128,
U8 if clamped => Descriptor::ClampedU8,
U8 => Descriptor::U8,
U16 => Descriptor::U16,
U32 => Descriptor::U32,
U64 => Descriptor::U64,
+ U128 => Descriptor::U128,
F32 => Descriptor::F32,
F64 => Descriptor::F64,
BOOLEAN => Descriptor::Boolean,
@@ -132,6 +152,15 @@ impl Descriptor {
CLOSURE => Descriptor::Closure(Box::new(Closure::decode(data))),
REF => Descriptor::Ref(Box::new(Descriptor::_decode(data, clamped))),
REFMUT => Descriptor::RefMut(Box::new(Descriptor::_decode(data, clamped))),
+ LONGREF => {
+ // This descriptor basically just serves as a macro, where most things
+ // become normal `Ref`s, but long refs to externrefs become owned.
+ let contents = Descriptor::_decode(data, clamped);
+ match contents {
+ Descriptor::Externref | Descriptor::NamedExternref(_) => contents,
+ _ => Descriptor::Ref(Box::new(contents)),
+ }
+ }
SLICE => Descriptor::Slice(Box::new(Descriptor::_decode(data, clamped))),
VECTOR => Descriptor::Vector(Box::new(Descriptor::_decode(data, clamped))),
OPTIONAL => Descriptor::Option(Box::new(Descriptor::_decode(data, clamped))),
@@ -139,7 +168,22 @@ impl Descriptor {
CACHED_STRING => Descriptor::CachedString,
STRING => Descriptor::String,
EXTERNREF => Descriptor::Externref,
- ENUM => Descriptor::Enum { hole: get(data) },
+ ENUM => {
+ let name = get_string(data);
+ let hole = get(data);
+ Descriptor::Enum { name, hole }
+ }
+ STRING_ENUM => {
+ let name = get_string(data);
+ let variant_count = get(data);
+ let invalid = variant_count;
+ let hole = variant_count + 1;
+ Descriptor::StringEnum {
+ name,
+ invalid,
+ hole,
+ }
+ }
RUST_STRUCT => {
let name = get_string(data);
Descriptor::RustStruct(name)
@@ -151,6 +195,7 @@ impl Descriptor {
CHAR => Descriptor::Char,
UNIT => Descriptor::Unit,
CLAMPED => Descriptor::_decode(data, true),
+ NONNULL => Descriptor::NonNull,
other => panic!("unknown descriptor: {}", other),
}
}
@@ -263,7 +308,11 @@ impl VectorKind {
VectorKind::F64 => "Float64Array".to_string(),
VectorKind::Externref => "any[]".to_string(),
VectorKind::NamedExternref(ref name) => {
- format!("({})[]", name)
+ if is_valid_ident(name.as_str()) {
+ format!("{}[]", name)
+ } else {
+ format!("({})[]", name)
+ }
}
}
}
diff --git a/crates/cli-support/src/descriptors.rs b/crates/cli-support/src/descriptors.rs
index 2486ff8d059..6dec401463f 100644
--- a/crates/cli-support/src/descriptors.rs
+++ b/crates/cli-support/src/descriptors.rs
@@ -3,7 +3,7 @@
//! The purpose of this module is to basically execute a pass on a raw wasm
//! module that just came out of the compiler. The pass will execute all
//! relevant descriptor functions contained in the module which wasm-bindgen
-//! uses to convey type infomation here, to the CLI.
+//! uses to convey type information here, to the CLI.
//!
//! All descriptor functions are removed after this pass runs and in their stead
//! a new custom section, defined in this module, is inserted into the
@@ -11,10 +11,10 @@
//! functions.
use crate::descriptor::{Closure, Descriptor};
-use anyhow::Error;
+use anyhow::{bail, Error};
use std::borrow::Cow;
-use std::collections::{HashMap, HashSet};
-use walrus::ImportId;
+use std::collections::HashMap;
+use walrus::{ConstExpr, ElementItems, ElementKind, ImportId, RefType};
use walrus::{CustomSection, FunctionId, Module, TypedCustomSectionId};
use wasm_bindgen_wasm_interpreter::Interpreter;
@@ -22,7 +22,6 @@ use wasm_bindgen_wasm_interpreter::Interpreter;
pub struct WasmBindgenDescriptorsSection {
pub descriptors: HashMap,
pub closure_imports: HashMap,
- cached_closures: HashMap,
}
pub type WasmBindgenDescriptorsSectionId = TypedCustomSectionId;
@@ -88,7 +87,7 @@ impl WasmBindgenDescriptorsSection {
// Find all functions which call `wbindgen_describe_closure`. These are
// specially codegen'd so we know the rough structure of them. For each
// one we delegate to the interpreter to figure out the actual result.
- let mut element_removal_list = HashSet::new();
+ let mut element_removal_list = HashMap::new();
let mut func_to_descriptor = HashMap::new();
for (id, local) in module.funcs.iter_local() {
let mut find = FindDescribeClosure {
@@ -107,9 +106,55 @@ impl WasmBindgenDescriptorsSection {
// For all indirect functions that were closure descriptors, delete them
// from the function table since we've executed them and they're not
// necessary in the final binary.
- for (segment, idx) in element_removal_list {
- log::trace!("delete element {}", idx);
- module.elements.get_mut(segment).members[idx] = None;
+ for (segment, idxs) in element_removal_list {
+ let segment = module.elements.get_mut(segment);
+
+ let items = match &mut segment.items {
+ ElementItems::Functions(items) => items,
+ ElementItems::Expressions(_, items) => {
+ for idx in idxs {
+ log::trace!("delete element {}", idx);
+ items[idx] = ConstExpr::RefNull(RefType::Funcref)
+ }
+
+ continue;
+ }
+ };
+
+ let (table, offset) = match &segment.kind {
+ ElementKind::Active {
+ table,
+ offset: ConstExpr::Value(Value::I32(n)),
+ } => (*table, *n),
+ _ => bail!("somehow found a closure in an unexpected element segment"),
+ };
+
+ let mut to_insert = Vec::new();
+
+ for idx in idxs.into_iter().rev() {
+ log::trace!("delete element {}", idx);
+
+ items.remove(idx);
+
+ // Last item, no need to do anything fancy.
+ if items.len() == idx {
+ continue;
+ }
+
+ let block = items.split_off(idx);
+ let offset = offset + idx as i32 + 1;
+ let offset = ConstExpr::Value(Value::I32(offset));
+
+ to_insert.push((offset, block));
+ }
+
+ for (offset, block) in to_insert.into_iter().rev() {
+ let id = module.elements.add(
+ ElementKind::Active { table, offset },
+ ElementItems::Functions(block),
+ );
+ module.tables.get_mut(table).elem_segments.insert(id);
+ }
}
// And finally replace all calls of `wbindgen_describe_closure` with a
@@ -120,21 +165,12 @@ impl WasmBindgenDescriptorsSection {
let mut items = func_to_descriptor.into_iter().collect::>();
items.sort_by_key(|i| i.0);
for (func, descriptor) in items {
- // This uses a cache so that if the same closure exists multiple times it will
- // deduplicate it so it only exists once.
- let id = match self.cached_closures.get(&descriptor) {
- Some(id) => *id,
- None => {
- let import_name = format!("__wbindgen_closure_wrapper{}", func.index());
- let (id, import_id) =
- module.add_import_func("__wbindgen_placeholder__", &import_name, ty);
- module.funcs.get_mut(id).name = Some(import_name);
- self.closure_imports
- .insert(import_id, descriptor.clone().unwrap_closure());
- self.cached_closures.insert(descriptor, id);
- id
- }
- };
+ let import_name = format!("__wbindgen_closure_wrapper{}", func.index());
+ let (id, import_id) =
+ module.add_import_func("__wbindgen_placeholder__", &import_name, ty);
+ module.funcs.get_mut(id).name = Some(import_name);
+ self.closure_imports
+ .insert(import_id, descriptor.clone().unwrap_closure());
let local = match &mut module.funcs.get_mut(func).kind {
walrus::FunctionKind::Local(l) => l,
@@ -157,12 +193,18 @@ impl WasmBindgenDescriptorsSection {
found: bool,
}
- impl<'a> Visitor<'a> for FindDescribeClosure {
+ impl Visitor<'_> for FindDescribeClosure {
fn visit_call(&mut self, call: &Call) {
if call.func == self.wbindgen_describe_closure {
self.found = true;
}
}
+
+ fn visit_return_call(&mut self, instr: &walrus::ir::ReturnCall) {
+ if instr.func == self.wbindgen_describe_closure {
+ self.found = true;
+ }
+ }
}
struct UpdateDescribeClosure {
@@ -170,12 +212,18 @@ impl WasmBindgenDescriptorsSection {
replacement: FunctionId,
}
- impl<'a> VisitorMut for UpdateDescribeClosure {
+ impl VisitorMut for UpdateDescribeClosure {
fn visit_call_mut(&mut self, call: &mut Call) {
if call.func == self.wbindgen_describe_closure {
call.func = self.replacement;
}
}
+
+ fn visit_return_call_mut(&mut self, instr: &mut walrus::ir::ReturnCall) {
+ if instr.func == self.wbindgen_describe_closure {
+ instr.func = self.replacement;
+ }
+ }
}
}
}
diff --git a/crates/cli-support/src/externref.rs b/crates/cli-support/src/externref.rs
index 8aff6a4853a..845780c844f 100644
--- a/crates/cli-support/src/externref.rs
+++ b/crates/cli-support/src/externref.rs
@@ -5,7 +5,8 @@ use crate::wit::{AdapterKind, Instruction, NonstandardWitSection};
use crate::wit::{AdapterType, InstructionData, StackChange, WasmBindgenAux};
use anyhow::Result;
use std::collections::HashMap;
-use walrus::{ir::Value, ElementKind, InitExpr, Module};
+use walrus::ElementItems;
+use walrus::{ir::Value, ConstExpr, ElementKind, Module};
use wasm_bindgen_externref_xform::Context;
pub fn process(module: &mut Module) -> Result<()> {
@@ -25,12 +26,12 @@ pub fn process(module: &mut Module) -> Result<()> {
// Transform all exported functions in the module, using the bindings listed
// for each exported function.
- for (id, adapter) in section.adapters.iter_mut() {
+ for (id, adapter) in &mut section.adapters {
let instructions = match &mut adapter.kind {
AdapterKind::Local { instructions } => instructions,
AdapterKind::Import { .. } => continue,
};
- if let Some(id) = implements.get(&id) {
+ if let Some(id) = implements.get(id) {
import_xform(
&mut cfg,
*id,
@@ -74,10 +75,10 @@ pub fn process(module: &mut Module) -> Result<()> {
aux.externref_drop_slice = meta.drop_slice;
}
- // Additonally we may need to update some adapter instructions other than
+ // Additionally we may need to update some adapter instructions other than
// those found for the externref pass. These are some general "fringe support"
// things necessary to get absolutely everything working.
- for (_, adapter) in section.adapters.iter_mut() {
+ for adapter in &mut section.adapters.values_mut() {
let instrs = match &mut adapter.kind {
AdapterKind::Local { instructions } => instructions,
AdapterKind::Import { .. } => continue,
@@ -85,7 +86,7 @@ pub fn process(module: &mut Module) -> Result<()> {
for instr in instrs {
match instr.instr {
// Calls to the heap live count intrinsic are now routed to the
- // actual wasm function which keeps track of this.
+ // actual Wasm function which keeps track of this.
Instruction::CallAdapter(adapter) => {
let id = match meta.live_count {
Some(id) => id,
@@ -99,10 +100,10 @@ pub fn process(module: &mut Module) -> Result<()> {
AuxImport::Intrinsic(Intrinsic::ExternrefHeapLiveCount) => {}
_ => continue,
}
- instr.instr = Instruction::Standard(wit_walrus::Instruction::CallCore(id));
+ instr.instr = Instruction::CallCore(id);
}
- // Optional externref values are now managed in the wasm module, so
+ // Optional externref values are now managed in the Wasm module, so
// we need to store where they're managed.
Instruction::I32FromOptionExternref {
ref mut table_and_alloc,
@@ -110,7 +111,10 @@ pub fn process(module: &mut Module) -> Result<()> {
*table_and_alloc = meta.alloc.map(|id| (meta.table, id));
}
- Instruction::UnwrapResult {
+ Instruction::ExternrefLoadOwned {
+ ref mut table_and_drop,
+ }
+ | Instruction::UnwrapResult {
ref mut table_and_drop,
}
| Instruction::UnwrapResultString {
@@ -118,6 +122,7 @@ pub fn process(module: &mut Module) -> Result<()> {
} => {
*table_and_drop = meta.drop.map(|id| (meta.table, id));
}
+ Instruction::CachedStringLoad { ref mut table, .. } => *table = Some(meta.table),
_ => continue,
};
}
@@ -132,7 +137,7 @@ fn find_call_export(instrs: &[InstructionData]) -> Option {
instrs
.iter()
.enumerate()
- .filter_map(|(i, instr)| match instr.instr {
+ .find_map(|(i, instr)| match instr.instr {
Instruction::CallExport(e) => Some(Export::Export(e)),
Instruction::CallTableElement(e) => Some(Export::TableElement {
idx: e,
@@ -140,7 +145,6 @@ fn find_call_export(instrs: &[InstructionData]) -> Option {
}),
_ => None,
})
- .next()
}
enum Export {
@@ -174,14 +178,11 @@ fn import_xform(
let mut to_delete = Vec::new();
let mut iter = instrs.iter().enumerate();
let mut args = Vec::new();
- while let Some((i, instr)) = iter.next() {
+ for (i, instr) in iter.by_ref() {
match instr.instr {
Instruction::CallAdapter(_) => break,
- Instruction::ExternrefLoadOwned | Instruction::TableGet => {
- let owned = match instr.instr {
- Instruction::TableGet => false,
- _ => true,
- };
+ Instruction::ExternrefLoadOwned { .. } | Instruction::TableGet => {
+ let owned = !matches!(instr.instr, Instruction::TableGet);
let mut arg: Arg = match args.pop().unwrap() {
Some(arg) => arg,
None => panic!("previous instruction must be `arg.get`"),
@@ -195,7 +196,7 @@ fn import_xform(
args.push(Some(arg));
to_delete.push(i);
}
- Instruction::Standard(wit_walrus::Instruction::ArgGet(n)) => {
+ Instruction::ArgGet(n) => {
args.push(Some(Arg {
idx: n as usize,
externref: None,
@@ -218,19 +219,13 @@ fn import_xform(
}
let mut ret_externref = false;
- while let Some((i, instr)) = iter.next() {
- match instr.instr {
- Instruction::I32FromExternrefOwned => {
- assert_eq!(results.len(), 1);
- match results[0] {
- AdapterType::I32 => {}
- _ => panic!("must be `i32` type"),
- }
- results[0] = AdapterType::Externref;
- ret_externref = true;
- to_delete.push(i);
- }
- _ => {}
+ for (i, instr) in iter {
+ if matches!(instr.instr, Instruction::I32FromExternrefOwned) {
+ assert_eq!(results.len(), 1);
+ assert!(matches!(results[0], AdapterType::I32), "must be `i32` type");
+ results[0] = AdapterType::Externref;
+ ret_externref = true;
+ to_delete.push(i);
}
}
@@ -270,7 +265,7 @@ fn export_xform(cx: &mut Context, export: Export, instrs: &mut Vec break,
Instruction::I32FromExternrefOwned => {
@@ -299,13 +294,16 @@ fn export_xform(cx: &mut Context, export: Export, instrs: &mut Vec {
+ Instruction::LoadRetptr { .. } => uses_retptr = true,
+ Instruction::ExternrefLoadOwned { .. } if !uses_retptr => {
ret_externref = true;
to_delete.push(i);
}
@@ -335,7 +333,7 @@ fn export_xform(cx: &mut Context, export: Export, instrs: &mut Vec 0 {
+ // indices directly into the Wasm module.
+ if !aux.imports_with_catch.is_empty() {
return true;
}
@@ -359,41 +357,37 @@ fn module_needs_externref_metadata(aux: &WasmBindgenAux, section: &NonstandardWi
AdapterKind::Local { instructions } => instructions,
AdapterKind::Import { .. } => return false,
};
- instructions.iter().any(|instr| match instr.instr {
- VectorToMemory {
- kind: VectorKind::Externref | VectorKind::NamedExternref(_),
- ..
- }
- | MutableSliceToMemory {
- kind: VectorKind::Externref | VectorKind::NamedExternref(_),
- ..
- }
- | OptionVector {
- kind: VectorKind::Externref | VectorKind::NamedExternref(_),
- ..
- }
- | VectorLoad {
- kind: VectorKind::Externref | VectorKind::NamedExternref(_),
- ..
- }
- | OptionVectorLoad {
- kind: VectorKind::Externref | VectorKind::NamedExternref(_),
- ..
- }
- | View {
- kind: VectorKind::Externref | VectorKind::NamedExternref(_),
- ..
- }
- | OptionView {
- kind: VectorKind::Externref | VectorKind::NamedExternref(_),
- ..
- } => true,
- _ => false,
+ instructions.iter().any(|instr| {
+ matches!(
+ instr.instr,
+ VectorToMemory {
+ kind: VectorKind::Externref | VectorKind::NamedExternref(_),
+ ..
+ } | MutableSliceToMemory {
+ kind: VectorKind::Externref | VectorKind::NamedExternref(_),
+ ..
+ } | OptionVector {
+ kind: VectorKind::Externref | VectorKind::NamedExternref(_),
+ ..
+ } | VectorLoad {
+ kind: VectorKind::Externref | VectorKind::NamedExternref(_),
+ ..
+ } | OptionVectorLoad {
+ kind: VectorKind::Externref | VectorKind::NamedExternref(_),
+ ..
+ } | View {
+ kind: VectorKind::Externref | VectorKind::NamedExternref(_),
+ ..
+ } | OptionView {
+ kind: VectorKind::Externref | VectorKind::NamedExternref(_),
+ ..
+ }
+ )
})
})
}
-/// In MVP wasm all element segments must be contiguous lists of function
+/// In MVP Wasm all element segments must be contiguous lists of function
/// indices. Post-MVP with reference types element segments can have holes.
/// While `walrus` will select the encoding that fits, this function forces the
/// listing of segments to be MVP-compatible.
@@ -404,11 +398,22 @@ pub fn force_contiguous_elements(module: &mut Module) -> Result<()> {
// Here we take a look at all element segments in the module to see if we
// need to split them.
for segment in module.elements.iter_mut() {
- // If this segment has all-`Some` members then it's alrady contiguous
- // and we can skip it.
- if segment.members.iter().all(|m| m.is_some()) {
- continue;
- }
+ let (ty, items) = match &mut segment.items {
+ ElementItems::Expressions(ty, items) => {
+ // If this segment has no null reference members then it's already
+ // contiguous and we can skip it.
+ if items
+ .iter()
+ .all(|item| !matches!(item, ConstExpr::RefNull(_)))
+ {
+ continue;
+ }
+
+ (*ty, items)
+ }
+ // Function index segments don't have holes.
+ ElementItems::Functions(_) => continue,
+ };
// For now active segments are all we're interested in since
// passive/declared have no hope of being MVP-compatible anyway.
@@ -417,7 +422,7 @@ pub fn force_contiguous_elements(module: &mut Module) -> Result<()> {
let (table, offset) = match &segment.kind {
ElementKind::Active {
table,
- offset: InitExpr::Value(Value::I32(n)),
+ offset: ConstExpr::Value(Value::I32(n)),
} => (*table, *n),
_ => continue,
};
@@ -432,16 +437,13 @@ pub fn force_contiguous_elements(module: &mut Module) -> Result<()> {
// offset.
let mut commit = |last_idx: usize, block: Vec<_>| {
let new_offset = offset + (last_idx - block.len()) as i32;
- let new_offset = InitExpr::Value(Value::I32(new_offset));
- new_segments.push((table, new_offset, segment.ty, block));
+ let new_offset = ConstExpr::Value(Value::I32(new_offset));
+ new_segments.push((table, new_offset, ty, block));
};
- for (i, id) in segment.members.iter().enumerate() {
- match id {
- // If we find a function, then we either start a new block or
- // push it onto the existing block.
- Some(id) => block.get_or_insert(Vec::new()).push(Some(*id)),
- None => {
- let block = match block.take() {
+ for (i, expr) in items.iter().enumerate() {
+ match expr {
+ ConstExpr::RefNull(_) => {
+ let block: Vec<_> = match block.take() {
Some(b) => b,
None => continue,
};
@@ -456,21 +458,25 @@ pub fn force_contiguous_elements(module: &mut Module) -> Result<()> {
commit(i, block);
}
}
+ // If we find a function, then we either start a new block or
+ // push it onto the existing block.
+ _ => block.get_or_insert(Vec::new()).push(*expr),
}
}
// If there's no trailing empty slots then we commit the last block onto
// the new segment list.
if let Some(block) = block {
- commit(segment.members.len(), block);
+ commit(items.len(), block);
}
- segment.members.truncate(truncate);
+ items.truncate(truncate);
}
for (table, offset, ty, members) in new_segments {
- let id = module
- .elements
- .add(ElementKind::Active { table, offset }, ty, members);
+ let id = module.elements.add(
+ ElementKind::Active { table, offset },
+ ElementItems::Expressions(ty, members),
+ );
module.tables.get_mut(table).elem_segments.insert(id);
}
Ok(())
diff --git a/crates/cli-support/src/intrinsic.rs b/crates/cli-support/src/intrinsic.rs
index efdc131ca21..6141fdb4a52 100644
--- a/crates/cli-support/src/intrinsic.rs
+++ b/crates/cli-support/src/intrinsic.rs
@@ -51,15 +51,6 @@ macro_rules! intrinsics {
)*
}
}
-
- /// Returns the symbol name of this intrinsic
- pub fn name(&self) -> &'static str {
- match self {
- $(
- Intrinsic::$name => $sym,
- )*
- }
- }
}
};
}
@@ -80,6 +71,18 @@ fn opt_f64() -> Descriptor {
Descriptor::Option(Box::new(Descriptor::F64))
}
+fn opt_i64() -> Descriptor {
+ Descriptor::Option(Box::new(Descriptor::I64))
+}
+
+fn slice(contents: Descriptor) -> Descriptor {
+ Descriptor::Ref(Box::new(Descriptor::Slice(Box::new(contents))))
+}
+
+fn vector(contents: Descriptor) -> Descriptor {
+ Descriptor::Vector(Box::new(contents))
+}
+
intrinsics! {
pub enum Intrinsic {
#[symbol = "__wbindgen_jsval_eq"]
@@ -91,6 +94,9 @@ intrinsics! {
#[symbol = "__wbindgen_is_function"]
#[signature = fn(ref_externref()) -> Boolean]
IsFunction,
+ #[symbol = "__wbindgen_is_array"]
+ #[signature = fn(ref_externref()) -> Boolean]
+ IsArray,
#[symbol = "__wbindgen_is_undefined"]
#[signature = fn(ref_externref()) -> Boolean]
IsUndefined,
@@ -193,9 +199,24 @@ intrinsics! {
#[symbol = "__wbindgen_number_new"]
#[signature = fn(F64) -> Externref]
NumberNew,
- #[symbol = "__wbindgen_bigint_new"]
+ #[symbol = "__wbindgen_bigint_from_str"]
#[signature = fn(ref_string()) -> Externref]
- BigIntNew,
+ BigIntFromStr,
+ #[symbol = "__wbindgen_bigint_from_i64"]
+ #[signature = fn(I64) -> Externref]
+ BigIntFromI64,
+ #[symbol = "__wbindgen_bigint_from_u64"]
+ #[signature = fn(U64) -> Externref]
+ BigIntFromU64,
+ #[symbol = "__wbindgen_bigint_from_i128"]
+ #[signature = fn(I64, U64) -> Externref]
+ BigIntFromI128,
+ #[symbol = "__wbindgen_bigint_from_u128"]
+ #[signature = fn(U64, U64) -> Externref]
+ BigIntFromU128,
+ #[symbol = "__wbindgen_bigint_get_as_i64"]
+ #[signature = fn(ref_externref()) -> opt_i64()]
+ BigIntGetAsI64,
#[symbol = "__wbindgen_string_new"]
#[signature = fn(ref_string()) -> Externref]
StringNew,
@@ -226,6 +247,9 @@ intrinsics! {
#[symbol = "__wbindgen_memory"]
#[signature = fn() -> Externref]
Memory,
+ #[symbol = "__wbindgen_exports"]
+ #[signature = fn() -> Externref]
+ Exports,
#[symbol = "__wbindgen_module"]
#[signature = fn() -> Externref]
Module,
@@ -241,6 +265,48 @@ intrinsics! {
#[symbol = "__wbindgen_json_serialize"]
#[signature = fn(ref_externref()) -> String]
JsonSerialize,
+ #[symbol = "__wbindgen_copy_to_typed_array"]
+ #[signature = fn(slice(U8), ref_externref()) -> Unit]
+ CopyToTypedArray,
+ #[symbol = "__wbindgen_uint8_array_new"]
+ #[signature = fn(vector(U8)) -> Externref]
+ Uint8ArrayNew,
+ #[symbol = "__wbindgen_uint8_clamped_array_new"]
+ #[signature = fn(vector(ClampedU8)) -> Externref]
+ Uint8ClampedArrayNew,
+ #[symbol = "__wbindgen_uint16_array_new"]
+ #[signature = fn(vector(U16)) -> Externref]
+ Uint16ArrayNew,
+ #[symbol = "__wbindgen_uint32_array_new"]
+ #[signature = fn(vector(U32)) -> Externref]
+ Uint32ArrayNew,
+ #[symbol = "__wbindgen_biguint64_array_new"]
+ #[signature = fn(vector(U64)) -> Externref]
+ BigUint64ArrayNew,
+ #[symbol = "__wbindgen_int8_array_new"]
+ #[signature = fn(vector(I8)) -> Externref]
+ Int8ArrayNew,
+ #[symbol = "__wbindgen_int16_array_new"]
+ #[signature = fn(vector(I16)) -> Externref]
+ Int16ArrayNew,
+ #[symbol = "__wbindgen_int32_array_new"]
+ #[signature = fn(vector(I32)) -> Externref]
+ Int32ArrayNew,
+ #[symbol = "__wbindgen_bigint64_array_new"]
+ #[signature = fn(vector(I64)) -> Externref]
+ BigInt64ArrayNew,
+ #[symbol = "__wbindgen_float32_array_new"]
+ #[signature = fn(vector(F32)) -> Externref]
+ Float32ArrayNew,
+ #[symbol = "__wbindgen_float64_array_new"]
+ #[signature = fn(vector(F64)) -> Externref]
+ Float64ArrayNew,
+ #[symbol = "__wbindgen_array_new"]
+ #[signature = fn() -> Externref]
+ ArrayNew,
+ #[symbol = "__wbindgen_array_push"]
+ #[signature = fn(ref_externref(), Externref) -> Unit]
+ ArrayPush,
#[symbol = "__wbindgen_externref_heap_live_count"]
#[signature = fn() -> I32]
ExternrefHeapLiveCount,
diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs
index 9eb290ae350..09501a67b40 100644
--- a/crates/cli-support/src/js/binding.rs
+++ b/crates/cli-support/src/js/binding.rs
@@ -6,9 +6,13 @@
use crate::js::Context;
use crate::wit::InstructionData;
-use crate::wit::{Adapter, AdapterId, AdapterKind, AdapterType, Instruction};
+use crate::wit::{
+ Adapter, AdapterId, AdapterKind, AdapterType, AuxFunctionArgumentData, Instruction,
+};
use anyhow::{anyhow, bail, Error};
-use walrus::Module;
+use std::collections::HashSet;
+use std::fmt::Write;
+use walrus::{Module, ValType};
/// A one-size-fits-all builder for processing WebIDL bindings and generating
/// JS.
@@ -35,10 +39,16 @@ pub struct JsBuilder<'a, 'b> {
/// JS functions, etc.
cx: &'a mut Context<'b>,
+ /// A debug name for the function being generated, used for error messages
+ debug_name: &'a str,
+
/// The "prelude" of the function, or largely just the JS function we've
/// built so far.
prelude: String,
+ /// Code which should go before the `try {` in a try-finally block.
+ pre_try: String,
+
/// JS code to execute in a `finally` block in case any exceptions happen.
finally: String,
@@ -50,7 +60,7 @@ pub struct JsBuilder<'a, 'b> {
/// use to translate the `arg.get` instruction.
args: Vec,
- /// The wasm interface types "stack". The expressions pushed onto this stack
+ /// The Wasm interface types "stack". The expressions pushed onto this stack
/// are intended to be *pure*, and if they're not, they should be pushed
/// into the `prelude`, assigned to a variable, and the variable should be
/// pushed to the stack. We're not super principled about this though, so
@@ -62,13 +72,26 @@ pub struct JsFunction {
pub code: String,
pub ts_sig: String,
pub js_doc: String,
+ pub ts_doc: String,
pub ts_arg_tys: Vec,
pub ts_ret_ty: Option,
+ pub ts_refs: HashSet,
+ /// Whether this function has a single optional argument.
+ ///
+ /// If the function is a setter, that means that the field it sets is optional.
pub might_be_optional_field: bool,
pub catch: bool,
pub log_error: bool,
}
+/// A references to an (likely) exported symbol used in TS type expression.
+///
+/// Right now, only string enum require this type of anaylsis.
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub enum TsReference {
+ StringEnum(String),
+}
+
impl<'a, 'b> Builder<'a, 'b> {
pub fn new(cx: &'a mut Context<'b>) -> Builder<'a, 'b> {
Builder {
@@ -100,8 +123,13 @@ impl<'a, 'b> Builder<'a, 'b> {
&mut self,
adapter: &Adapter,
instructions: &[InstructionData],
- explicit_arg_names: &Option>,
+ args_data: &Option>,
asyncness: bool,
+ variadic: bool,
+ generate_jsdoc: bool,
+ debug_name: &str,
+ ret_ty_override: &Option,
+ ret_desc: &Option,
) -> Result {
if self
.cx
@@ -119,30 +147,31 @@ impl<'a, 'b> Builder<'a, 'b> {
// If this is a method then we're generating this as part of a class
// method, so the leading parameter is the this pointer stored on
// the JS object, so synthesize that here.
- let mut js = JsBuilder::new(self.cx);
- match self.method {
- Some(consumes_self) => {
- drop(params.next());
- if js.cx.config.debug {
- js.prelude(
- "if (this.ptr == 0) throw new Error('Attempt to use a moved value');",
- );
- }
- if consumes_self {
- js.prelude("const ptr = this.__destroy_into_raw();");
- js.args.push("ptr".into());
- } else {
- js.args.push("this.ptr".into());
- }
+ let mut js = JsBuilder::new(self.cx, debug_name);
+ if let Some(consumes_self) = self.method {
+ let _ = params.next();
+ if js.cx.config.debug {
+ js.prelude(
+ "if (this.__wbg_ptr == 0) throw new Error('Attempt to use a moved value');",
+ );
+ }
+ if consumes_self {
+ js.prelude("const ptr = this.__destroy_into_raw();");
+ js.args.push("ptr".into());
+ } else {
+ js.args.push("this.__wbg_ptr".into());
}
- None => {}
}
for (i, param) in params.enumerate() {
- let arg = match explicit_arg_names {
+ let arg = match args_data {
Some(list) => list[i].clone(),
- None => format!("arg{}", i),
+ None => AuxFunctionArgumentData {
+ name: format!("arg{}", i),
+ ty_override: None,
+ desc: None,
+ },
};
- js.args.push(arg.clone());
+ js.args.push(arg.name.clone());
function_args.push(arg);
arg_tys.push(param);
}
@@ -158,12 +187,22 @@ impl<'a, 'b> Builder<'a, 'b> {
// We don't actually manage a literal stack at runtime, but instead we
// act as more of a compiler to generate straight-line code to make it
// more JIT-friendly. The generated code should be equivalent to the
- // wasm interface types stack machine, however.
+ // Wasm interface types stack machine, however.
for instr in instructions {
- instruction(&mut js, &instr.instr, &mut self.log_error)?;
- }
-
- assert_eq!(js.stack.len(), adapter.results.len());
+ instruction(
+ &mut js,
+ &instr.instr,
+ &mut self.log_error,
+ &self.constructor,
+ )?;
+ }
+
+ assert_eq!(
+ js.stack.len(),
+ adapter.results.len(),
+ "stack size mismatch for {}",
+ debug_name
+ );
match js.stack.len() {
0 => {}
1 => {
@@ -192,14 +231,28 @@ impl<'a, 'b> Builder<'a, 'b> {
// }
let mut code = String::new();
- code.push_str("(");
- code.push_str(&function_args.join(", "));
- code.push_str(") {\n");
+ code.push('(');
+ for (i, v) in function_args.iter().enumerate() {
+ if i != 0 {
+ code.push_str(", ");
+ }
+
+ if variadic && i == function_args.len() - 1 {
+ code.push_str("...");
+ }
- let mut call = js.prelude;
- if js.finally.len() != 0 {
- call = format!("try {{\n{}}} finally {{\n{}}}\n", call, js.finally);
+ code.push_str(&v.name);
}
+ code.push_str(") {\n");
+
+ let call = if !js.finally.is_empty() {
+ format!(
+ "{}try {{\n{}}} finally {{\n{}}}\n",
+ js.pre_try, js.prelude, js.finally
+ )
+ } else {
+ js.pre_try + &js.prelude
+ };
if self.catch {
js.cx.expose_handle_error()?;
@@ -214,27 +267,51 @@ impl<'a, 'b> Builder<'a, 'b> {
}
code.push_str(&call);
- code.push_str("}");
+ code.push('}');
// Rust Structs' fields converted into Getter and Setter functions before
// we decode them from webassembly, finding if a function is a field
// should start from here. Struct fields(Getter) only have one arg, and
// this is the clue we can infer if a function might be a field.
let mut might_be_optional_field = false;
- let (ts_sig, ts_arg_tys, ts_ret_ty) = self.typescript_signature(
+ let (ts_sig, ts_arg_tys, ts_ret_ty, ts_refs) = self.typescript_signature(
&function_args,
&arg_tys,
&adapter.inner_results,
&mut might_be_optional_field,
asyncness,
+ variadic,
+ ret_ty_override,
);
- let js_doc = self.js_doc_comments(&function_args, &arg_tys, &ts_ret_ty);
+ let js_doc = if generate_jsdoc {
+ self.js_doc_comments(
+ &function_args,
+ &arg_tys,
+ &ts_ret_ty,
+ variadic,
+ ret_ty_override,
+ ret_desc,
+ )
+ } else {
+ String::new()
+ };
+
+ // generate ts_doc
+ // ts doc is slightly different than js doc, where there is no
+ // arguments types followed after @param tag, as well as no special
+ // casings for arguments names such as "@param {string} [arg]" that
+ // tags the argument as optional, for ts doc we only need arg names
+ // and rest are just derived from function ts signature
+ let ts_doc = self.ts_doc_comments(&function_args, ret_desc);
+
Ok(JsFunction {
code,
ts_sig,
js_doc,
+ ts_doc,
ts_arg_tys,
ts_ret_ty,
+ ts_refs,
might_be_optional_field,
catch: self.catch,
log_error: self.log_error,
@@ -248,17 +325,26 @@ impl<'a, 'b> Builder<'a, 'b> {
/// return value, it doesn't include the function name in any way.
fn typescript_signature(
&self,
- arg_names: &[String],
+ args_data: &[AuxFunctionArgumentData],
arg_tys: &[&AdapterType],
result_tys: &[AdapterType],
might_be_optional_field: &mut bool,
asyncness: bool,
- ) -> (String, Vec, Option) {
+ variadic: bool,
+ ret_ty_override: &Option,
+ ) -> (String, Vec, Option, HashSet) {
// Build up the typescript signature as well
let mut omittable = true;
let mut ts_args = Vec::new();
let mut ts_arg_tys = Vec::new();
- for (name, ty) in arg_names.iter().zip(arg_tys).rev() {
+ let mut ts_refs = HashSet::new();
+ for (
+ AuxFunctionArgumentData {
+ name, ty_override, ..
+ },
+ ty,
+ ) in args_data.iter().zip(arg_tys).rev()
+ {
// In TypeScript, we can mark optional parameters as omittable
// using the `?` suffix, but only if they're not followed by
// non-omittable parameters. Therefore iterate the parameter list
@@ -266,15 +352,23 @@ impl<'a, 'b> Builder<'a, 'b> {
// soon as a non-optional parameter is encountered.
let mut arg = name.to_string();
let mut ts = String::new();
- match ty {
- AdapterType::Option(ty) if omittable => {
- arg.push_str("?: ");
- adapter2ts(ty, &mut ts);
- }
- ty => {
- omittable = false;
- arg.push_str(": ");
- adapter2ts(ty, &mut ts);
+ if let Some(v) = ty_override {
+ omittable = false;
+ arg.push_str(": ");
+ ts.push_str(v);
+ } else {
+ match ty {
+ AdapterType::Option(ty) if omittable => {
+ // e.g. `foo?: string | null`
+ arg.push_str("?: ");
+ adapter2ts(ty, TypePosition::Argument, &mut ts, Some(&mut ts_refs));
+ ts.push_str(" | null");
+ }
+ ty => {
+ adapter2ts(ty, TypePosition::Argument, &mut ts, Some(&mut ts_refs));
+ omittable = false;
+ arg.push_str(": ");
+ }
}
}
arg.push_str(&ts);
@@ -283,7 +377,19 @@ impl<'a, 'b> Builder<'a, 'b> {
}
ts_args.reverse();
ts_arg_tys.reverse();
- let mut ts = format!("({})", ts_args.join(", "));
+ let mut ts = String::from("(");
+ if variadic {
+ if let Some((last, non_variadic_args)) = ts_args.split_last() {
+ ts.push_str(&non_variadic_args.join(", "));
+ if !non_variadic_args.is_empty() {
+ ts.push_str(", ");
+ }
+ ts.push_str((String::from("...") + last).as_str())
+ }
+ } else {
+ ts.push_str(&ts_args.join(", "));
+ };
+ ts.push(')');
// If this function is an optional field's setter, it should have only
// one arg, and omittable should be `true`.
@@ -296,10 +402,19 @@ impl<'a, 'b> Builder<'a, 'b> {
if self.constructor.is_none() {
ts.push_str(": ");
let mut ret = String::new();
- match result_tys.len() {
- 0 => ret.push_str("void"),
- 1 => adapter2ts(&result_tys[0], &mut ret),
- _ => ret.push_str("[any]"),
+ if let Some(v) = &ret_ty_override {
+ ret.push_str(v);
+ } else {
+ match result_tys.len() {
+ 0 => ret.push_str("void"),
+ 1 => adapter2ts(
+ &result_tys[0],
+ TypePosition::Return,
+ &mut ret,
+ Some(&mut ts_refs),
+ ),
+ _ => ret.push_str("[any]"),
+ }
}
if asyncness {
ret = format!("Promise<{}>", ret);
@@ -307,40 +422,149 @@ impl<'a, 'b> Builder<'a, 'b> {
ts.push_str(&ret);
ts_ret = Some(ret);
}
- return (ts, ts_arg_tys, ts_ret);
+ (ts, ts_arg_tys, ts_ret, ts_refs)
}
/// Returns a helpful JS doc comment which lists types for all parameters
/// and the return value.
fn js_doc_comments(
&self,
- arg_names: &[String],
+ args_data: &[AuxFunctionArgumentData],
arg_tys: &[&AdapterType],
ts_ret: &Option,
+ variadic: bool,
+ ret_ty_override: &Option,
+ ret_desc: &Option,
) -> String {
- let mut ret = String::new();
- for (name, ty) in arg_names.iter().zip(arg_tys) {
- ret.push_str("@param {");
- adapter2ts(ty, &mut ret);
+ let (variadic_arg, fn_arg_names) = match args_data.split_last() {
+ Some((last, args)) if variadic => (Some(last), args),
+ _ => (None, args_data),
+ };
+
+ let mut omittable = true;
+ let mut js_doc_args = Vec::new();
+
+ for (
+ AuxFunctionArgumentData {
+ name,
+ ty_override,
+ desc,
+ },
+ ty,
+ ) in fn_arg_names.iter().zip(arg_tys).rev()
+ {
+ let mut arg = "@param {".to_string();
+
+ if let Some(v) = ty_override {
+ omittable = false;
+ arg.push_str(v);
+ arg.push_str("} ");
+ arg.push_str(name);
+ } else {
+ match ty {
+ AdapterType::Option(ty) if omittable => {
+ adapter2ts(ty, TypePosition::Argument, &mut arg, None);
+ arg.push_str(" | null} ");
+ arg.push('[');
+ arg.push_str(name);
+ arg.push(']');
+ }
+ _ => {
+ omittable = false;
+ adapter2ts(ty, TypePosition::Argument, &mut arg, None);
+ arg.push_str("} ");
+ arg.push_str(name);
+ }
+ }
+ }
+ // append description
+ if let Some(v) = desc {
+ arg.push_str(" - ");
+ arg.push_str(v);
+ }
+ arg.push('\n');
+ js_doc_args.push(arg);
+ }
+
+ let mut ret: String = js_doc_args.into_iter().rev().collect();
+
+ if let (
+ Some(AuxFunctionArgumentData {
+ name,
+ ty_override,
+ desc,
+ }),
+ Some(ty),
+ ) = (variadic_arg, arg_tys.last())
+ {
+ ret.push_str("@param {...");
+ if let Some(v) = ty_override {
+ ret.push_str(v);
+ } else {
+ adapter2ts(ty, TypePosition::Argument, &mut ret, None);
+ }
ret.push_str("} ");
ret.push_str(name);
- ret.push_str("\n");
+
+ // append desc
+ if let Some(v) = desc {
+ ret.push_str(" - ");
+ ret.push_str(v);
+ }
+ ret.push('\n');
}
- if let Some(ts) = ts_ret {
- if ts != "void" {
+ if let Some(ts) = ret_ty_override.as_ref().or(ts_ret.as_ref()) {
+ // skip if type is void and there is no description
+ if ts != "void" || ret_desc.is_some() {
ret.push_str(&format!("@returns {{{}}}", ts));
}
+ // append return description
+ if let Some(v) = ret_desc {
+ ret.push(' ');
+ ret.push_str(v);
+ }
}
ret
}
+
+ /// Returns a helpful TS doc comment which lists all parameters and
+ /// the return value descriptions.
+ fn ts_doc_comments(
+ &self,
+ args_data: &[AuxFunctionArgumentData],
+ ret_desc: &Option,
+ ) -> String {
+ let mut ts_doc = String::new();
+ // ofc we dont need arg type for ts doc, only arg name
+ for AuxFunctionArgumentData { name, desc, .. } in args_data.iter() {
+ ts_doc.push_str("@param ");
+ ts_doc.push_str(name);
+
+ // append desc
+ if let Some(v) = desc {
+ ts_doc.push_str(" - ");
+ ts_doc.push_str(v);
+ }
+ ts_doc.push('\n');
+ }
+
+ // only if there is return description, as we dont want empty @return tag
+ if let Some(ret_desc) = ret_desc {
+ ts_doc.push_str("@returns ");
+ ts_doc.push_str(ret_desc);
+ }
+ ts_doc
+ }
}
impl<'a, 'b> JsBuilder<'a, 'b> {
- pub fn new(cx: &'a mut Context<'b>) -> JsBuilder<'a, 'b> {
+ pub fn new(cx: &'a mut Context<'b>, debug_name: &'a str) -> JsBuilder<'a, 'b> {
JsBuilder {
cx,
+ debug_name,
args: Vec::new(),
tmp: 0,
+ pre_try: String::new(),
finally: String::new(),
prelude: String::new(),
stack: Vec::new(),
@@ -355,7 +579,7 @@ impl<'a, 'b> JsBuilder<'a, 'b> {
for line in prelude.trim().lines().map(|l| l.trim()) {
if !line.is_empty() {
self.prelude.push_str(line);
- self.prelude.push_str("\n");
+ self.prelude.push('\n');
}
}
}
@@ -364,7 +588,7 @@ impl<'a, 'b> JsBuilder<'a, 'b> {
for line in finally.trim().lines().map(|l| l.trim()) {
if !line.is_empty() {
self.finally.push_str(line);
- self.finally.push_str("\n");
+ self.finally.push('\n');
}
}
}
@@ -372,11 +596,14 @@ impl<'a, 'b> JsBuilder<'a, 'b> {
pub fn tmp(&mut self) -> usize {
let ret = self.tmp;
self.tmp += 1;
- return ret;
+ ret
}
fn pop(&mut self) -> String {
- self.stack.pop().unwrap()
+ match self.stack.pop() {
+ Some(s) => s,
+ None => panic!("popping an empty stack in {}", self.debug_name),
+ }
}
fn push(&mut self, arg: String) {
@@ -396,6 +623,14 @@ impl<'a, 'b> JsBuilder<'a, 'b> {
self.prelude(&format!("_assertNum({});", arg));
}
+ fn assert_bigint(&mut self, arg: &str) {
+ if !self.cx.config.debug {
+ return;
+ }
+ self.cx.expose_assert_bigint();
+ self.prelude(&format!("_assertBigInt({});", arg));
+ }
+
fn assert_bool(&mut self, arg: &str) {
if !self.cx.config.debug {
return;
@@ -414,6 +649,26 @@ impl<'a, 'b> JsBuilder<'a, 'b> {
self.prelude("}");
}
+ fn assert_non_null(&mut self, arg: &str) {
+ self.cx.expose_assert_non_null();
+ self.prelude(&format!("_assertNonNull({});", arg));
+ }
+
+ fn assert_char(&mut self, arg: &str) {
+ self.cx.expose_assert_char();
+ self.prelude(&format!("_assertChar({});", arg));
+ }
+
+ fn assert_optional_bigint(&mut self, arg: &str) {
+ if !self.cx.config.debug {
+ return;
+ }
+ self.cx.expose_is_like_none();
+ self.prelude(&format!("if (!isLikeNone({})) {{", arg));
+ self.assert_bigint(arg);
+ self.prelude("}");
+ }
+
fn assert_optional_bool(&mut self, arg: &str) {
if !self.cx.config.debug {
return;
@@ -430,7 +685,7 @@ impl<'a, 'b> JsBuilder<'a, 'b> {
}
self.prelude(&format!(
"\
- if ({0}.ptr === 0) {{
+ if ({0}.__wbg_ptr === 0) {{
throw new Error('Attempt to use a moved value');
}}
",
@@ -467,31 +722,89 @@ impl<'a, 'b> JsBuilder<'a, 'b> {
}
}
-fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) -> Result<(), Error> {
+fn instruction(
+ js: &mut JsBuilder,
+ instr: &Instruction,
+ log_error: &mut bool,
+ constructor: &Option,
+) -> Result<(), Error> {
+ fn wasm_to_string_enum(name: &str, index: &str) -> String {
+ // e.g. ["a","b","c"][someIndex]
+ format!("__wbindgen_enum_{name}[{index}]")
+ }
+ fn string_enum_to_wasm(name: &str, invalid: u32, enum_val: &str) -> String {
+ // e.g. (["a","b","c"].indexOf(someEnumVal) + 1 || 4) - 1
+ // |
+ // invalid + 1
+ //
+ // The idea is that `indexOf` returns -1 if someEnumVal is invalid,
+ // and with +1 we get 0 which is falsey, so we can use || to
+ // substitute invalid+1. Finally, we just do -1 to get the correct
+ // values for everything.
+ format!(
+ "(__wbindgen_enum_{name}.indexOf({enum_val}) + 1 || {invalid}) - 1",
+ invalid = invalid + 1
+ )
+ }
+
+ fn int128_to_int64x2(val: &str) -> (String, String) {
+ // we don't need to perform any conversion here, because the JS
+ // WebAssembly API will automatically convert the bigints to 64 bits
+ // for us. This even allows us to ignore signedness.
+ let low = val.to_owned();
+ let high = format!("{val} >> BigInt(64)");
+ (low, high)
+ }
+ fn int64x2_to_int128(low: String, high: String, signed: bool) -> String {
+ let low = format!("BigInt.asUintN(64, {low})");
+ if signed {
+ format!("({low} | ({high} << BigInt(64)))")
+ } else {
+ format!("({low} | (BigInt.asUintN(64, {high}) << BigInt(64)))")
+ }
+ }
+
match instr {
- Instruction::Standard(wit_walrus::Instruction::ArgGet(n)) => {
+ Instruction::ArgGet(n) => {
let arg = js.arg(*n).to_string();
js.push(arg);
}
- Instruction::Standard(wit_walrus::Instruction::CallAdapter(_)) => {
- panic!("standard call adapter functions should be mapped to our adapters");
- }
-
- Instruction::Standard(wit_walrus::Instruction::CallCore(_))
+ Instruction::CallCore(_)
| Instruction::CallExport(_)
| Instruction::CallAdapter(_)
| Instruction::CallTableElement(_)
- | Instruction::Standard(wit_walrus::Instruction::DeferCallCore(_)) => {
+ | Instruction::DeferFree { .. } => {
let invoc = Invocation::from(instr, js.cx.module)?;
- let (params, results) = invoc.params_results(js.cx);
+ let (mut params, results) = invoc.params_results(js.cx);
- // Pop off the number of parameters for the function we're calling
let mut args = Vec::new();
- for _ in 0..params {
- args.push(js.pop());
+ let tmp = js.tmp();
+ if invoc.defer() {
+ if let Instruction::DeferFree { .. } = instr {
+ // Ignore `free`'s final `align` argument, since that's manually inserted later.
+ params -= 1;
+ }
+ // If the call is deferred, the arguments to the function still need to be
+ // accessible in the `finally` block, so we declare variables to hold the args
+ // outside of the try-finally block and then set those to the args.
+ for (i, arg) in js.stack[js.stack.len() - params..].iter().enumerate() {
+ let name = format!("deferred{tmp}_{i}");
+ writeln!(js.pre_try, "let {name};").unwrap();
+ writeln!(js.prelude, "{name} = {arg};").unwrap();
+ args.push(name);
+ }
+ if let Instruction::DeferFree { align, .. } = instr {
+ // add alignment
+ args.push(align.to_string());
+ }
+ } else {
+ // Otherwise, pop off the number of parameters for the function we're calling.
+ for _ in 0..params {
+ args.push(js.pop());
+ }
+ args.reverse();
}
- args.reverse();
// Call the function through an export of the underlying module.
let call = invoc.invoke(js.cx, &args, &mut js.prelude, log_error)?;
@@ -502,7 +815,6 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) ->
match (invoc.defer(), results) {
(true, 0) => {
js.finally(&format!("{};", call));
- js.stack.extend(args);
}
(true, _) => panic!("deferred calls must have no results"),
(false, 0) => js.prelude(&format!("{};", call)),
@@ -519,43 +831,111 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) ->
}
}
- Instruction::Standard(wit_walrus::Instruction::IntToWasm { trap: false, .. }) => {
+ Instruction::Int32ToWasm => {
let val = js.pop();
js.assert_number(&val);
js.push(val);
}
+ Instruction::WasmToInt32 { unsigned_32 } => {
+ let val = js.pop();
+ if *unsigned_32 {
+ // When converting to a JS number we need to specially handle the `u32`
+ // case because if the high bit is set then it comes out as a negative
+ // number, but we want to switch that to an unsigned representation.
+ js.push(format!("{} >>> 0", val))
+ } else {
+ js.push(val)
+ }
+ }
- // When converting to a JS number we need to specially handle the `u32`
- // case because if the high bit is set then it comes out as a negative
- // number, but we want to switch that to an unsigned representation.
- Instruction::Standard(wit_walrus::Instruction::WasmToInt {
- trap: false,
- output,
- ..
- }) => {
+ Instruction::Int64ToWasm => {
let val = js.pop();
- match output {
- wit_walrus::ValType::U32 => js.push(format!("{} >>> 0", val)),
- _ => js.push(val),
+ js.assert_bigint(&val);
+ js.push(val);
+ }
+ Instruction::WasmToInt64 { unsigned } => {
+ let val = js.pop();
+ if *unsigned {
+ js.push(format!("BigInt.asUintN(64, {val})"))
+ } else {
+ js.push(val)
}
}
- Instruction::Standard(wit_walrus::Instruction::WasmToInt { trap: true, .. })
- | Instruction::Standard(wit_walrus::Instruction::IntToWasm { trap: true, .. }) => {
- bail!("trapping wasm-to-int and int-to-wasm instructions not supported")
+ Instruction::Int128ToWasm => {
+ let val = js.pop();
+ js.assert_bigint(&val);
+ let (low, high) = int128_to_int64x2(&val);
+ js.push(low);
+ js.push(high);
+ }
+ Instruction::WasmToInt128 { signed } => {
+ let high = js.pop();
+ let low = js.pop();
+ js.push(int64x2_to_int128(low, high, *signed));
+ }
+
+ Instruction::OptionInt128ToWasm => {
+ let val = js.pop();
+ js.cx.expose_is_like_none();
+ js.assert_optional_bigint(&val);
+ let (low, high) = int128_to_int64x2(&val);
+ js.push(format!("!isLikeNone({val})"));
+ js.push(format!("isLikeNone({val}) ? BigInt(0) : {low}"));
+ js.push(format!("isLikeNone({val}) ? BigInt(0) : {high}"));
+ }
+ Instruction::OptionWasmToInt128 { signed } => {
+ let high = js.pop();
+ let low = js.pop();
+ let present = js.pop();
+ let val = int64x2_to_int128(low, high, *signed);
+ js.push(format!("{present} === 0 ? undefined : {val}"));
+ }
+
+ Instruction::WasmToStringEnum { name } => {
+ let index = js.pop();
+ js.cx.expose_string_enum(name);
+ js.push(wasm_to_string_enum(name, &index))
+ }
+
+ Instruction::OptionWasmToStringEnum { name } => {
+ // Since hole is currently variant_count+1 and the lookup is
+ // ["a","b","c"][index], the lookup will implicitly return map
+ // the hole to undefined, because OOB indexes will return undefined.
+ let index = js.pop();
+ js.cx.expose_string_enum(name);
+ js.push(wasm_to_string_enum(name, &index))
}
- Instruction::Standard(wit_walrus::Instruction::MemoryToString(mem)) => {
+ Instruction::StringEnumToWasm { name, invalid } => {
+ let enum_val = js.pop();
+ js.cx.expose_string_enum(name);
+ js.push(string_enum_to_wasm(name, *invalid, &enum_val))
+ }
+
+ Instruction::OptionStringEnumToWasm {
+ name,
+ invalid,
+ hole,
+ } => {
+ let enum_val = js.pop();
+ js.cx.expose_string_enum(name);
+ let enum_val_expr = string_enum_to_wasm(name, *invalid, &enum_val);
+ js.cx.expose_is_like_none();
+
+ // e.g. isLikeNone(someEnumVal) ? 4 : (string_enum_to_wasm(someEnumVal))
+ js.push(format!(
+ "isLikeNone({enum_val}) ? {hole} : ({enum_val_expr})"
+ ))
+ }
+
+ Instruction::MemoryToString(mem) => {
let len = js.pop();
let ptr = js.pop();
let get = js.cx.expose_get_string_from_wasm(*mem)?;
js.push(format!("{}({}, {})", get, ptr, len));
}
- Instruction::Standard(wit_walrus::Instruction::StringToMemory { mem, malloc }) => {
- js.string_to_memory(*mem, *malloc, None)?;
- }
-
Instruction::StringToMemory {
mem,
malloc,
@@ -575,18 +955,21 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) ->
}
Instruction::StoreRetptr { ty, offset, mem } => {
- let (mem, size) = match ty {
- AdapterType::I32 => (js.cx.expose_int32_memory(*mem), 4),
- AdapterType::F32 => (js.cx.expose_f32_memory(*mem), 4),
- AdapterType::F64 => (js.cx.expose_f64_memory(*mem), 8),
+ let mem = js.cx.expose_dataview_memory(*mem);
+ let (method, size) = match ty {
+ AdapterType::I32 => ("setInt32", 4),
+ AdapterType::I64 => ("setBigInt64", 8),
+ AdapterType::F32 => ("setFloat32", 4),
+ AdapterType::F64 => ("setFloat64", 8),
other => bail!("invalid aggregate return type {:?}", other),
};
// Note that we always assume the return pointer is argument 0,
// which is currently the case for LLVM.
let val = js.pop();
let expr = format!(
- "{}()[{} / {} + {}] = {};",
+ "{}().{}({} + {} * {}, {}, true);",
mem,
+ method,
js.arg(0),
size,
offset,
@@ -596,10 +979,12 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) ->
}
Instruction::LoadRetptr { ty, offset, mem } => {
- let (mem, quads) = match ty {
- AdapterType::I32 => (js.cx.expose_int32_memory(*mem), 1),
- AdapterType::F32 => (js.cx.expose_f32_memory(*mem), 1),
- AdapterType::F64 => (js.cx.expose_f64_memory(*mem), 2),
+ let mem = js.cx.expose_dataview_memory(*mem);
+ let (method, quads) = match ty {
+ AdapterType::I32 => ("getInt32", 1),
+ AdapterType::I64 => ("getBigInt64", 2),
+ AdapterType::F32 => ("getFloat32", 1),
+ AdapterType::F64 => ("getFloat64", 2),
other => bail!("invalid aggregate return type {:?}", other),
};
let size = quads * 4;
@@ -609,7 +994,10 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) ->
// If we're loading from the return pointer then we must have pushed
// it earlier, and we always push the same value, so load that value
// here
- let expr = format!("{}()[retptr / {} + {}]", mem, size, scaled_offset);
+ let expr = format!(
+ "{}().{}(retptr + {} * {}, true)",
+ mem, method, size, scaled_offset
+ );
js.prelude(&format!("var r{} = {};", offset, expr));
js.push(format!("r{}", offset));
}
@@ -623,7 +1011,11 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) ->
Instruction::I32FromStringFirstChar => {
let val = js.pop();
- js.push(format!("{}.codePointAt(0)", val));
+ let i = js.tmp();
+ js.prelude(&format!("const char{i} = {val}.codePointAt(0);"));
+ let val = format!("char{i}");
+ js.assert_char(&val);
+ js.push(val);
}
Instruction::I32FromExternrefOwned => {
@@ -642,19 +1034,18 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) ->
Instruction::I32FromExternrefRustOwned { class } => {
let val = js.pop();
- js.assert_class(&val, &class);
+ js.assert_class(&val, class);
js.assert_not_moved(&val);
let i = js.tmp();
- js.prelude(&format!("var ptr{} = {}.ptr;", i, val));
- js.prelude(&format!("{}.ptr = 0;", val));
+ js.prelude(&format!("var ptr{} = {}.__destroy_into_raw();", i, val));
js.push(format!("ptr{}", i));
}
Instruction::I32FromExternrefRustBorrow { class } => {
let val = js.pop();
- js.assert_class(&val, &class);
+ js.assert_class(&val, class);
js.assert_not_moved(&val);
- js.push(format!("{}.ptr", val));
+ js.push(format!("{}.__wbg_ptr", val));
}
Instruction::I32FromOptionRust { class } => {
@@ -665,58 +1056,11 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) ->
js.prelude(&format!("if (!isLikeNone({0})) {{", val));
js.assert_class(&val, class);
js.assert_not_moved(&val);
- js.prelude(&format!("ptr{} = {}.ptr;", i, val));
- js.prelude(&format!("{}.ptr = 0;", val));
+ js.prelude(&format!("ptr{} = {}.__destroy_into_raw();", i, val));
js.prelude("}");
js.push(format!("ptr{}", i));
}
- Instruction::I32Split64 { signed } => {
- let val = js.pop();
- let f = if *signed {
- js.cx.expose_int64_cvt_shim()
- } else {
- js.cx.expose_uint64_cvt_shim()
- };
- let i = js.tmp();
- js.prelude(&format!(
- "
- {f}[0] = {val};
- const low{i} = u32CvtShim[0];
- const high{i} = u32CvtShim[1];
- ",
- i = i,
- f = f,
- val = val,
- ));
- js.push(format!("low{}", i));
- js.push(format!("high{}", i));
- }
-
- Instruction::I32SplitOption64 { signed } => {
- let val = js.pop();
- js.cx.expose_is_like_none();
- let f = if *signed {
- js.cx.expose_int64_cvt_shim()
- } else {
- js.cx.expose_uint64_cvt_shim()
- };
- let i = js.tmp();
- js.prelude(&format!(
- "\
- {f}[0] = isLikeNone({val}) ? BigInt(0) : {val};
- const low{i} = u32CvtShim[0];
- const high{i} = u32CvtShim[1];
- ",
- i = i,
- f = f,
- val = val,
- ));
- js.push(format!("!isLikeNone({0})", val));
- js.push(format!("low{}", i));
- js.push(format!("high{}", i));
- }
-
Instruction::I32FromOptionExternref { table_and_alloc } => {
let val = js.pop();
js.cx.expose_is_like_none();
@@ -748,11 +1092,18 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) ->
Instruction::I32FromOptionChar => {
let val = js.pop();
+ let i = js.tmp();
js.cx.expose_is_like_none();
- js.push(format!(
- "isLikeNone({0}) ? 0xFFFFFF : {0}.codePointAt(0)",
+ js.prelude(&format!(
+ "const char{i} = isLikeNone({0}) ? 0xFFFFFF : {0}.codePointAt(0);",
val
));
+ let val = format!("char{i}");
+ js.cx.expose_assert_char();
+ js.prelude(&format!(
+ "if ({val} !== 0xFFFFFF) {{ _assertChar({val}); }}"
+ ));
+ js.push(val);
}
Instruction::I32FromOptionEnum { hole } => {
@@ -762,12 +1113,63 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) ->
js.push(format!("isLikeNone({0}) ? {1} : {0}", val, hole));
}
- Instruction::FromOptionNative { .. } => {
+ Instruction::F64FromOptionSentinelInt { signed } => {
+ let val = js.pop();
+ js.cx.expose_is_like_none();
+ js.assert_optional_number(&val);
+
+ // We need to convert the given number to a 32-bit integer before
+ // passing it to the ABI for 2 reasons:
+ // 1. Rust's behavior for `value_f64 as i32/u32` is different from
+ // the WebAssembly behavior for values outside the 32-bit range.
+ // We could implement this behavior in Rust too, but it's easier
+ // to do it in JS.
+ // 2. If we allowed values outside the 32-bit range, the sentinel
+ // value itself would be allowed. This would make it impossible
+ // to distinguish between the sentinel value and a valid value.
+ //
+ // To perform the actual conversion, we use JS bit shifts. Handily,
+ // >> and >>> perform a conversion to i32 and u32 respectively
+ // to apply the bit shift, so we can use e.g. x >>> 0 to convert to
+ // u32.
+
+ let op = if *signed { ">>" } else { ">>>" };
+ js.push(format!("isLikeNone({val}) ? 0x100000001 : ({val}) {op} 0"));
+ }
+ Instruction::F64FromOptionSentinelF32 => {
let val = js.pop();
js.cx.expose_is_like_none();
js.assert_optional_number(&val);
+
+ // Similar to the above 32-bit integer variant, we convert the
+ // number to a 32-bit *float* before passing it to the ABI. This
+ // ensures consistent behavior with WebAssembly and makes it
+ // possible to use a sentinel value.
+
+ js.push(format!(
+ "isLikeNone({val}) ? 0x100000001 : Math.fround({val})"
+ ));
+ }
+
+ Instruction::FromOptionNative { ty } => {
+ let val = js.pop();
+ js.cx.expose_is_like_none();
+ if *ty == ValType::I64 {
+ js.assert_optional_bigint(&val);
+ } else {
+ js.assert_optional_number(&val);
+ }
js.push(format!("!isLikeNone({0})", val));
- js.push(format!("isLikeNone({0}) ? 0 : {0}", val));
+ js.push(format!(
+ "isLikeNone({val}) ? {zero} : {val}",
+ zero = if *ty == ValType::I64 {
+ // We can't use bigint literals for now. See:
+ // https://github.com/rustwasm/wasm-bindgen/issues/4246
+ "BigInt(0)"
+ } else {
+ "0"
+ }
+ ));
}
Instruction::VectorToMemory { kind, malloc, mem } => {
@@ -895,15 +1297,8 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) ->
js.push(format!("len{}", i));
}
- Instruction::MutableSliceToMemory {
- kind,
- malloc,
- mem,
- free,
- } => {
- // First up, pass the JS value into wasm, getting out a pointer and
- // a length. These two pointer/length values get pushed onto the
- // value stack.
+ Instruction::MutableSliceToMemory { kind, malloc, mem } => {
+ // Copy the contents of the typed array into wasm.
let val = js.pop();
let func = js.cx.pass_to_wasm_function(kind.clone(), *mem)?;
let malloc = js.cx.export_name_of(*malloc);
@@ -916,25 +1311,12 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) ->
malloc = malloc,
));
js.prelude(&format!("var len{} = WASM_VECTOR_LEN;", i));
+ // Then pass it the pointer and the length of where we copied it.
js.push(format!("ptr{}", i));
js.push(format!("len{}", i));
-
- // Next we set up a `finally` clause which will both update the
- // original mutable slice with any modifications, and then free the
- // Rust-backed memory.
- let free = js.cx.export_name_of(*free);
- let get = js.cx.memview_function(kind.clone(), *mem);
- js.finally(&format!(
- "
- {val}.set({get}().subarray(ptr{i} / {size}, ptr{i} / {size} + len{i}));
- wasm.{free}(ptr{i}, len{i} * {size});
- ",
- val = val,
- get = get,
- free = free,
- size = kind.size(),
- i = i,
- ));
+ // Then we give Wasm a reference to the original typed array, so that it can
+ // update it with modifications made on the Wasm side before returning.
+ js.push(val);
}
Instruction::BoolFromI32 => {
@@ -942,10 +1324,17 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) ->
js.push(format!("{} !== 0", val));
}
- Instruction::ExternrefLoadOwned => {
- js.cx.expose_take_object();
+ Instruction::ExternrefLoadOwned { table_and_drop } => {
+ let take_object = if let Some((table, drop)) = *table_and_drop {
+ js.cx
+ .expose_take_from_externref_table(table, drop)?
+ .to_string()
+ } else {
+ js.cx.expose_take_object();
+ "takeObject".to_string()
+ };
let val = js.pop();
- js.push(format!("takeObject({})", val));
+ js.push(format!("{}({})", take_object, val));
}
Instruction::StringFromChar => {
@@ -953,62 +1342,53 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) ->
js.push(format!("String.fromCodePoint({})", val));
}
- Instruction::I64FromLoHi { signed } => {
- let f = if *signed {
- js.cx.expose_int64_cvt_shim()
- } else {
- js.cx.expose_uint64_cvt_shim()
- };
- let i = js.tmp();
- let high = js.pop();
- let low = js.pop();
- js.prelude(&format!(
- "\
- u32CvtShim[0] = {low};
- u32CvtShim[1] = {high};
- const n{i} = {f}[0];
- ",
- low = low,
- high = high,
- f = f,
- i = i,
- ));
- js.push(format!("n{}", i))
- }
-
Instruction::RustFromI32 { class } => {
- js.cx.require_class_wrap(class);
let val = js.pop();
- js.push(format!("{}.__wrap({})", class, val));
+ match constructor {
+ Some(name) if name == class => {
+ js.prelude(&format!(
+ "
+ this.__wbg_ptr = {val} >>> 0;
+ {name}Finalization.register(this, this.__wbg_ptr, this);
+ "
+ ));
+ js.push(String::from("this"));
+ }
+ Some(_) | None => {
+ js.cx.require_class_wrap(class);
+ js.push(format!("{}.__wrap({})", class, val));
+ }
+ }
}
Instruction::OptionRustFromI32 { class } => {
- js.cx.require_class_wrap(class);
+ assert!(constructor.is_none());
let val = js.pop();
+ js.cx.require_class_wrap(class);
js.push(format!(
"{0} === 0 ? undefined : {1}.__wrap({0})",
val, class,
- ))
+ ));
}
Instruction::CachedStringLoad {
owned,
- optional: _,
mem,
free,
+ table,
} => {
let len = js.pop();
let ptr = js.pop();
let tmp = js.tmp();
- let get = js.cx.expose_get_cached_string_from_wasm(*mem)?;
+ let get = js.cx.expose_get_cached_string_from_wasm(*mem, *table)?;
js.prelude(&format!("var v{} = {}({}, {});", tmp, get, ptr, len));
if *owned {
let free = js.cx.export_name_of(*free);
js.prelude(&format!(
- "if ({ptr} !== 0) {{ wasm.{}({ptr}, {len}); }}",
+ "if ({ptr} !== 0) {{ wasm.{}({ptr}, {len}, 1); }}",
free,
ptr = ptr,
len = len,
@@ -1081,11 +1461,11 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) ->
let free = js.cx.export_name_of(*free);
js.prelude(&format!("var v{} = {}({}, {}).slice();", i, f, ptr, len));
js.prelude(&format!(
- "wasm.{}({}, {} * {});",
+ "wasm.{}({}, {} * {size}, {size});",
free,
ptr,
len,
- kind.size()
+ size = kind.size()
));
js.push(format!("v{}", i))
}
@@ -1100,11 +1480,11 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) ->
js.prelude(&format!("if ({} !== 0) {{", ptr));
js.prelude(&format!("v{} = {}({}, {}).slice();", i, f, ptr, len));
js.prelude(&format!(
- "wasm.{}({}, {} * {});",
+ "wasm.{}({}, {} * {size}, {size});",
free,
ptr,
len,
- kind.size()
+ size = kind.size()
));
js.prelude("}");
js.push(format!("v{}", i));
@@ -1129,19 +1509,31 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) ->
));
}
+ Instruction::OptionF64Sentinel => {
+ let val = js.pop();
+ js.push(format!("{0} === 0x100000001 ? undefined : {0}", val));
+ }
+
Instruction::OptionU32Sentinel => {
let val = js.pop();
js.push(format!("{0} === 0xFFFFFF ? undefined : {0}", val));
}
- Instruction::ToOptionNative { ty: _, signed } => {
+ Instruction::ToOptionNative { ty, signed } => {
let val = js.pop();
let present = js.pop();
js.push(format!(
- "{} === 0 ? undefined : {}{}",
+ "{} === 0 ? undefined : {}",
present,
- val,
- if *signed { "" } else { " >>> 0" },
+ if *signed {
+ val
+ } else {
+ match ty {
+ ValType::I32 => format!("{val} >>> 0"),
+ ValType::I64 => format!("BigInt.asUintN(64, {val})"),
+ _ => unreachable!("unsigned non-integer"),
+ }
+ },
));
}
@@ -1163,29 +1555,22 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) ->
js.push(format!("{0} === {1} ? undefined : {0}", val, hole));
}
- Instruction::Option64FromI32 { signed } => {
- let f = if *signed {
- js.cx.expose_int64_cvt_shim()
- } else {
- js.cx.expose_uint64_cvt_shim()
- };
- let i = js.tmp();
- let high = js.pop();
- let low = js.pop();
- let present = js.pop();
- js.prelude(&format!(
- "
- u32CvtShim[0] = {low};
- u32CvtShim[1] = {high};
- const n{i} = {present} === 0 ? undefined : {f}[0];
- ",
- present = present,
- low = low,
- high = high,
- f = f,
- i = i,
- ));
- js.push(format!("n{}", i));
+ Instruction::I32FromNonNull => {
+ let val = js.pop();
+ js.assert_non_null(&val);
+ js.push(val);
+ }
+
+ Instruction::I32FromOptionNonNull => {
+ let val = js.pop();
+ js.cx.expose_is_like_none();
+ js.assert_optional_number(&val);
+ js.push(format!("isLikeNone({0}) ? 0 : {0}", val));
+ }
+
+ Instruction::OptionNonNullFromI32 => {
+ let val = js.pop();
+ js.push(format!("{0} === 0 ? undefined : {0} >>> 0", val));
}
}
Ok(())
@@ -1200,13 +1585,13 @@ impl Invocation {
fn from(instr: &Instruction, module: &Module) -> Result {
use Instruction::*;
Ok(match instr {
- Standard(wit_walrus::Instruction::CallCore(f)) => Invocation::Core {
+ CallCore(f) => Invocation::Core {
id: *f,
defer: false,
},
- Standard(wit_walrus::Instruction::DeferCallCore(f)) => Invocation::Core {
- id: *f,
+ DeferFree { free, .. } => Invocation::Core {
+ id: *free,
defer: true,
},
@@ -1284,7 +1669,18 @@ impl Invocation {
}
}
-fn adapter2ts(ty: &AdapterType, dst: &mut String) {
+#[derive(Debug, Clone, Copy)]
+enum TypePosition {
+ Argument,
+ Return,
+}
+
+fn adapter2ts(
+ ty: &AdapterType,
+ position: TypePosition,
+ dst: &mut String,
+ refs: Option<&mut HashSet>,
+) {
match ty {
AdapterType::I32
| AdapterType::S8
@@ -1294,18 +1690,34 @@ fn adapter2ts(ty: &AdapterType, dst: &mut String) {
| AdapterType::U16
| AdapterType::U32
| AdapterType::F32
- | AdapterType::F64 => dst.push_str("number"),
- AdapterType::I64 | AdapterType::S64 | AdapterType::U64 => dst.push_str("BigInt"),
+ | AdapterType::F64
+ | AdapterType::NonNull => dst.push_str("number"),
+ AdapterType::I64
+ | AdapterType::S64
+ | AdapterType::U64
+ | AdapterType::S128
+ | AdapterType::U128 => dst.push_str("bigint"),
AdapterType::String => dst.push_str("string"),
AdapterType::Externref => dst.push_str("any"),
AdapterType::Bool => dst.push_str("boolean"),
AdapterType::Vector(kind) => dst.push_str(&kind.js_ty()),
AdapterType::Option(ty) => {
- adapter2ts(ty, dst);
- dst.push_str(" | undefined");
+ adapter2ts(ty, position, dst, refs);
+ dst.push_str(match position {
+ TypePosition::Argument => " | null | undefined",
+ TypePosition::Return => " | undefined",
+ });
}
AdapterType::NamedExternref(name) => dst.push_str(name),
AdapterType::Struct(name) => dst.push_str(name),
+ AdapterType::Enum(name) => dst.push_str(name),
+ AdapterType::StringEnum(name) => {
+ if let Some(refs) = refs {
+ refs.insert(TsReference::StringEnum(name.clone()));
+ }
+
+ dst.push_str(name);
+ }
AdapterType::Function => dst.push_str("any"),
}
}
diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs
index ed540b0cfe3..3ba5be3a719 100644
--- a/crates/cli-support/src/js/mod.rs
+++ b/crates/cli-support/src/js/mod.rs
@@ -1,17 +1,23 @@
use crate::descriptor::VectorKind;
use crate::intrinsic::Intrinsic;
-use crate::wit::{Adapter, AdapterId, AdapterJsImportKind, AuxValue};
+use crate::wit::{
+ Adapter, AdapterId, AdapterJsImportKind, AuxExportedMethodKind, AuxReceiverKind, AuxStringEnum,
+ AuxValue,
+};
use crate::wit::{AdapterKind, Instruction, InstructionData};
use crate::wit::{AuxEnum, AuxExport, AuxExportKind, AuxImport, AuxStruct};
use crate::wit::{JsImport, JsImportName, NonstandardWitSection, WasmBindgenAux};
use crate::{reset_indentation, Bindgen, EncodeInto, OutputMode, PLACEHOLDER_MODULE};
use anyhow::{anyhow, bail, Context as _, Error};
+use binding::TsReference;
use std::borrow::Cow;
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::fmt;
+use std::fmt::Write;
use std::fs;
use std::path::{Path, PathBuf};
use walrus::{FunctionId, ImportId, MemoryId, Module, TableId, ValType};
+use wasm_bindgen_shared::identifier::is_valid_ident;
mod binding;
@@ -32,7 +38,7 @@ pub struct Context<'a> {
/// renames for each identifier.
js_imports: HashMap)>>,
- /// A map of each wasm import and what JS to hook up to it.
+ /// A map of each Wasm import and what JS to hook up to it.
wasm_import_definitions: HashMap,
/// A map from an import to the name we've locally imported it as.
@@ -43,40 +49,93 @@ pub struct Context<'a> {
/// identifiers.
defined_identifiers: HashMap,
+ /// A set of all (tracked) symbols referenced from within type definitions,
+ /// function signatures, etc.
+ typescript_refs: HashSet,
+
+ /// String enums that are used internally by the generated bindings.
+ ///
+ /// This tracks which string enums are used independently from whether their
+ /// type is used, because users may only use them in a way that doesn't
+ /// require the type or requires only the type.
+ used_string_enums: HashSet,
+
exported_classes: Option>,
/// A map of the name of npm dependencies we've loaded so far to the path
/// they're defined in as well as their version specification.
pub npm_dependencies: HashMap,
- /// A mapping of a index for memories as we see them. Used in function
- /// names.
- memory_indices: HashMap,
+ /// A mapping from the memory IDs as we see them to an index for that memory,
+ /// used in function names, as well as all the kinds of views we've created
+ /// of that memory.
+ ///
+ /// `BTreeMap` and `BTreeSet` are used to make the ordering deterministic.
+ memories: BTreeMap)>,
table_indices: HashMap,
/// A flag to track if the stack pointer setter shim has been injected.
stack_pointer_shim_injected: bool,
+
+ /// If threading is enabled.
+ threads_enabled: bool,
}
#[derive(Default)]
-pub struct ExportedClass {
+struct ExportedClass {
comments: String,
contents: String,
+ /// The TypeScript for the class's methods.
typescript: String,
+ /// Whether TypeScript for this class should be emitted (i.e., `skip_typescript` wasn't specified).
+ generate_typescript: bool,
has_constructor: bool,
wrap_needed: bool,
+ unwrap_needed: bool,
/// Whether to generate helper methods for inspecting the class
is_inspectable: bool,
/// All readable properties of the class
readable_properties: Vec,
- /// Map from field name to type as a string, docs plus whether it has a setter
- /// and it is optional
- typescript_fields: HashMap,
+ /// Map from field to information about those fields
+ typescript_fields: HashMap,
+}
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+struct FieldLocation {
+ name: String,
+ is_static: bool,
+}
+#[derive(Debug)]
+struct FieldInfo {
+ name: String,
+ is_static: bool,
+ order: usize,
+ getter: Option,
+ setter: Option,
+}
+/// A getter or setter for a field.
+#[derive(Debug)]
+struct FieldAccessor {
+ ty: String,
+ docs: String,
+ is_optional: bool,
+}
+
+/// Different JS constructs that can be exported.
+enum ExportJs<'a> {
+ /// A class of the form `class Name {...}`.
+ Class(&'a str),
+ /// An anonymous function expression of the form `function(...) {...}`.
+ ///
+ /// Note that the function name is not included in the string.
+ Function(&'a str),
+ /// An arbitrary JS expression.
+ Expression(&'a str),
}
const INITIAL_HEAP_VALUES: &[&str] = &["undefined", "null", "true", "false"];
// Must be kept in sync with `src/lib.rs` of the `wasm-bindgen` crate
-const INITIAL_HEAP_OFFSET: usize = 32;
+const INITIAL_HEAP_OFFSET: usize = 128;
impl<'a> Context<'a> {
pub fn new(
@@ -94,14 +153,17 @@ impl<'a> Context<'a> {
js_imports: Default::default(),
defined_identifiers: Default::default(),
wasm_import_definitions: Default::default(),
+ typescript_refs: Default::default(),
+ used_string_enums: Default::default(),
exported_classes: Some(Default::default()),
config,
+ threads_enabled: wasm_bindgen_threads_xform::is_enabled(module),
module,
npm_dependencies: Default::default(),
next_export_idx: 0,
wit,
aux,
- memory_indices: Default::default(),
+ memories: Default::default(),
table_indices: Default::default(),
stack_pointer_shim_injected: false,
})
@@ -114,43 +176,46 @@ impl<'a> Context<'a> {
fn export(
&mut self,
export_name: &str,
- contents: &str,
+ export: ExportJs,
comments: Option<&str>,
) -> Result<(), Error> {
let definition_name = self.generate_identifier(export_name);
- if contents.starts_with("class") && definition_name != export_name {
+ if matches!(export, ExportJs::Class(_)) && definition_name != export_name {
bail!("cannot shadow already defined class `{}`", export_name);
}
- let contents = contents.trim();
+ // write out comments
if let Some(c) = comments {
self.globals.push_str(c);
}
+
let global = match self.config.mode {
- OutputMode::Node {
- experimental_modules: false,
- } => {
- if contents.starts_with("class") {
- format!("{}\nmodule.exports.{1} = {1};\n", contents, export_name)
- } else {
- format!("module.exports.{} = {};\n", export_name, contents)
+ OutputMode::Node { module: false } => match export {
+ ExportJs::Class(class) => {
+ format!("{}\nmodule.exports.{1} = {1};\n", class, export_name)
}
- }
- OutputMode::NoModules { .. } => {
- if contents.starts_with("class") {
- format!("{}\n__exports.{1} = {1};\n", contents, export_name)
- } else {
- format!("__exports.{} = {};\n", export_name, contents)
+ ExportJs::Function(expr) | ExportJs::Expression(expr) => {
+ format!("module.exports.{} = {};\n", export_name, expr)
}
- }
+ },
+ OutputMode::NoModules { .. } => match export {
+ ExportJs::Class(class) => {
+ format!("{}\n__exports.{1} = {1};\n", class, export_name)
+ }
+ ExportJs::Function(expr) | ExportJs::Expression(expr) => {
+ format!("__exports.{} = {};\n", export_name, expr)
+ }
+ },
OutputMode::Bundler { .. }
- | OutputMode::Node {
- experimental_modules: true,
- }
+ | OutputMode::Node { module: true }
| OutputMode::Web
- | OutputMode::Deno => {
- if contents.starts_with("function") {
- let body = &contents[8..];
+ | OutputMode::Deno => match export {
+ ExportJs::Class(class) => {
+ assert_eq!(export_name, definition_name);
+ format!("export {}\n", class)
+ }
+ ExportJs::Function(function) => {
+ let body = function.strip_prefix("function").unwrap();
if export_name == definition_name {
format!("export function {}{}\n", export_name, body)
} else {
@@ -159,14 +224,12 @@ impl<'a> Context<'a> {
definition_name, body, definition_name, export_name,
)
}
- } else if contents.starts_with("class") {
- assert_eq!(export_name, definition_name);
- format!("export {}\n", contents)
- } else {
+ }
+ ExportJs::Expression(expr) => {
assert_eq!(export_name, definition_name);
- format!("export const {} = {};\n", export_name, contents)
+ format!("export const {} = {};\n", export_name, expr)
}
- }
+ },
};
self.global(&global);
Ok(())
@@ -197,31 +260,37 @@ impl<'a> Context<'a> {
fn generate_node_imports(&self) -> String {
let mut imports = BTreeSet::new();
- for import in self.module.imports.iter() {
+ for import in self
+ .module
+ .imports
+ .iter()
+ .filter(|i| !(matches!(i.kind, walrus::ImportKind::Memory(_))))
+ {
imports.insert(&import.module);
}
let mut shim = String::new();
- shim.push_str("let imports = {};\n");
+ shim.push_str("\nlet imports = {};\n");
- if self.config.mode.nodejs_experimental_modules() {
+ if self.config.mode.uses_es_modules() {
for (i, module) in imports.iter().enumerate() {
if module.as_str() != PLACEHOLDER_MODULE {
shim.push_str(&format!("import * as import{} from '{}';\n", i, module));
}
}
- }
-
- for (i, module) in imports.iter().enumerate() {
- if module.as_str() == PLACEHOLDER_MODULE {
- shim.push_str(&format!(
- "imports['{0}'] = module.exports;\n",
- PLACEHOLDER_MODULE
- ));
- } else {
- if self.config.mode.nodejs_experimental_modules() {
+ for (i, module) in imports.iter().enumerate() {
+ if module.as_str() != PLACEHOLDER_MODULE {
shim.push_str(&format!("imports['{}'] = import{};\n", module, i));
+ }
+ }
+ } else {
+ for module in imports.iter() {
+ if module.as_str() == PLACEHOLDER_MODULE {
+ shim.push_str(&format!(
+ "imports['{0}'] = module.exports;\n",
+ PLACEHOLDER_MODULE
+ ));
} else {
shim.push_str(&format!("imports['{0}'] = require('{0}');\n", module));
}
@@ -231,20 +300,37 @@ impl<'a> Context<'a> {
reset_indentation(&shim)
}
- fn generate_node_wasm_loading(&self, path: &Path) -> String {
+ fn generate_node_wasm_loading(&mut self, path: &Path) -> String {
let mut shim = String::new();
- if self.config.mode.nodejs_experimental_modules() {
+ let module_name = "wbg";
+ if let Some(mem) = self.module.memories.iter().next() {
+ if let Some(id) = mem.import {
+ self.module.imports.get_mut(id).module = module_name.to_string();
+ shim.push_str(&format!(
+ "imports.{module_name} = {{ memory: new WebAssembly.Memory({{"
+ ));
+ shim.push_str(&format!("initial:{}", mem.initial));
+ if let Some(max) = mem.maximum {
+ shim.push_str(&format!(",maximum:{}", max));
+ }
+ if mem.shared {
+ shim.push_str(",shared:true");
+ }
+ shim.push_str("}) };");
+ }
+ }
+
+ if self.config.mode.uses_es_modules() {
// On windows skip the leading `/` which comes out when we parse a
// url to use `C:\...` instead of `\C:\...`
shim.push_str(&format!(
"
- import * as path from 'path';
- import * as fs from 'fs';
- import * as url from 'url';
- import * as process from 'process';
+ import * as path from 'node:path';
+ import * as fs from 'node:fs';
+ import * as process from 'node:process';
- let file = path.dirname(url.parse(import.meta.url).pathname);
+ let file = path.dirname(new URL(import.meta.url).pathname);
if (process.platform === 'win32') {{
file = file.substring(1);
}}
@@ -252,6 +338,14 @@ impl<'a> Context<'a> {
",
path.file_name().unwrap().to_str().unwrap()
));
+ shim.push_str(
+ "
+ const wasmModule = new WebAssembly.Module(bytes);
+ const wasmInstance = new WebAssembly.Instance(wasmModule, imports);
+ const wasm = wasmInstance.exports;
+ export const __wasm = wasm;
+ ",
+ );
} else {
shim.push_str(&format!(
"
@@ -260,17 +354,16 @@ impl<'a> Context<'a> {
",
path.file_name().unwrap().to_str().unwrap()
));
+ shim.push_str(
+ "
+ const wasmModule = new WebAssembly.Module(bytes);
+ const wasmInstance = new WebAssembly.Instance(wasmModule, imports);
+ wasm = wasmInstance.exports;
+ module.exports.__wasm = wasm;
+ ",
+ );
}
- shim.push_str(
- "
- const wasmModule = new WebAssembly.Module(bytes);
- const wasmInstance = new WebAssembly.Instance(wasmModule, imports);
- wasm = wasmInstance.exports;
- module.exports.__wasm = wasm;
- ",
- );
-
reset_indentation(&shim)
}
@@ -294,7 +387,7 @@ impl<'a> Context<'a> {
wasm_import_object.push_str(&format!(" {}: {{\n", crate::PLACEHOLDER_MODULE));
- for (id, js) in crate::sorted_iter(&self.wasm_import_definitions) {
+ for (id, js) in iter_by_import(&self.wasm_import_definitions, self.module) {
let import = self.module.imports.get(*id);
wasm_import_object.push_str(&format!("{}: {},\n", &import.name, js.trim()));
}
@@ -337,7 +430,8 @@ impl<'a> Context<'a> {
}}
const wasmInstance = (await WebAssembly.instantiate(wasmCode, imports)).instance;
- const wasm = wasmInstance.exports;",
+ const wasm = wasmInstance.exports;
+ export const __wasm = wasm;",
module_name = module_name
)
}
@@ -359,7 +453,7 @@ impl<'a> Context<'a> {
}
// Depending on the output mode, generate necessary glue to actually
- // import the wasm file in one way or another.
+ // import the Wasm file in one way or another.
let mut init = (String::new(), String::new());
let mut footer = String::new();
let mut imports = self.js_import_header()?;
@@ -367,24 +461,33 @@ impl<'a> Context<'a> {
// In `--target no-modules` mode we need to both expose a name on
// the global object as well as generate our own custom start
// function.
+ // `document.currentScript` property can be null in browser extensions
OutputMode::NoModules { global } => {
js.push_str("const __exports = {};\n");
- js.push_str("let wasm;\n");
+ js.push_str("let script_src;\n");
+ js.push_str(
+ "\
+ if (typeof document !== 'undefined' && document.currentScript !== null) {
+ script_src = new URL(document.currentScript.src, location.href).toString();
+ }\n",
+ );
+ js.push_str("let wasm = undefined;\n");
init = self.gen_init(needs_manual_start, None)?;
- footer.push_str(&format!("{} = Object.assign(init, __exports);\n", global));
+ footer.push_str(&format!(
+ "{} = Object.assign(__wbg_init, {{ initSync }}, __exports);\n",
+ global
+ ));
}
// With normal CommonJS node we need to defer requiring the wasm
// until the end so most of our own exports are hooked up
- OutputMode::Node {
- experimental_modules: false,
- } => {
+ OutputMode::Node { module: false } => {
js.push_str(&self.generate_node_imports());
js.push_str("let wasm;\n");
- for (id, js) in crate::sorted_iter(&self.wasm_import_definitions) {
- let import = self.module.imports.get_mut(*id);
+ for (id, js) in iter_by_import(&self.wasm_import_definitions, self.module) {
+ let import = self.module.imports.get(*id);
footer.push_str("\nmodule.exports.");
footer.push_str(&import.name);
footer.push_str(" = ");
@@ -393,14 +496,14 @@ impl<'a> Context<'a> {
}
footer.push_str(
- &self.generate_node_wasm_loading(&Path::new(&format!(
+ &self.generate_node_wasm_loading(Path::new(&format!(
"./{}_bg.wasm",
module_name
))),
);
if needs_manual_start {
- footer.push_str("wasm.__wbindgen_start();\n");
+ footer.push_str("\nwasm.__wbindgen_start();\n");
}
}
@@ -411,27 +514,21 @@ impl<'a> Context<'a> {
footer.push_str(&self.generate_deno_wasm_loading(module_name));
+ footer.push_str("\n\n");
+
if needs_manual_start {
- footer.push_str("wasm.__wbindgen_start();\n");
+ footer.push_str("\nwasm.__wbindgen_start();\n");
}
}
- // With Bundlers and modern ES6 support in Node we can simply import
- // the wasm file as if it were an ES module and let the
- // bundler/runtime take care of it.
- OutputMode::Bundler { .. }
- | OutputMode::Node {
- experimental_modules: true,
- } => {
- imports.push_str(&format!(
- "import * as wasm from './{}_bg.wasm';\n",
- module_name
- ));
- for (id, js) in crate::sorted_iter(&self.wasm_import_definitions) {
+ // With Bundlers we can simply import the Wasm file as if it were an ES module
+ // and let the bundler/runtime take care of it.
+ // With Node we manually read the Wasm file from the filesystem and instantiate it.
+ OutputMode::Bundler { .. } | OutputMode::Node { module: true } => {
+ for (id, js) in iter_by_import(&self.wasm_import_definitions, self.module) {
let import = self.module.imports.get_mut(*id);
import.module = format!("./{}_bg.js", module_name);
- if js.starts_with("function") {
- let body = &js[8..];
+ if let Some(body) = js.strip_prefix("function") {
footer.push_str("\nexport function ");
footer.push_str(&import.name);
footer.push_str(body.trim());
@@ -444,19 +541,68 @@ impl<'a> Context<'a> {
footer.push_str(";\n");
}
}
+
+ match self.config.mode {
+ OutputMode::Bundler { .. } => {
+ self.imports_post.push_str(
+ "\
+ let wasm;
+ export function __wbg_set_wasm(val) {
+ wasm = val;
+ }
+ ",
+ );
+
+ start.get_or_insert_with(String::new).push_str(&format!(
+ "\
+import {{ __wbg_set_wasm }} from \"./{module_name}_bg.js\";
+__wbg_set_wasm(wasm);"
+ ));
+ }
+
+ OutputMode::Node { module: true } => {
+ self.imports_post.push_str(
+ "\
+ let wasm;
+ let wasmModule;
+ export function __wbg_set_wasm(exports, module) {
+ wasm = exports;
+ wasmModule = module;
+ }
+ ",
+ );
+
+ let start = start.get_or_insert_with(String::new);
+ start.push_str(&self.generate_node_imports());
+ start.push_str(&self.generate_node_wasm_loading(Path::new(&format!(
+ "./{}_bg.wasm",
+ module_name
+ ))));
+
+ start.push_str(&format!(
+ "imports[\"./{module_name}_bg.js\"].__wbg_set_wasm(wasm, wasmModule);"
+ ));
+ }
+
+ _ => {}
+ }
+
if needs_manual_start {
- start = Some("\nwasm.__wbindgen_start();\n".to_string());
+ start
+ .get_or_insert_with(String::new)
+ .push_str("\nwasm.__wbindgen_start();\n");
}
}
// With a browser-native output we're generating an ES module, but
- // browsers don't support natively importing wasm right now so we
+ // browsers don't support natively importing Wasm right now so we
// expose the same initialization function as `--target no-modules`
// as the default export of the module.
OutputMode::Web => {
self.imports_post.push_str("let wasm;\n");
init = self.gen_init(needs_manual_start, Some(&mut imports))?;
- footer.push_str("export default init;\n");
+ footer.push_str("export { initSync };\n");
+ footer.push_str("export default __wbg_init;");
}
}
@@ -464,7 +610,7 @@ impl<'a> Context<'a> {
// Not sure if this should happen in all cases, so just adding it to NoModules for now...
if self.config.mode.no_modules() {
ts = String::from("declare namespace wasm_bindgen {\n\t");
- ts.push_str(&self.typescript.replace("\n", "\n\t"));
+ ts.push_str(&self.typescript.replace('\n', "\n\t"));
ts.push_str("\n}\n");
} else {
ts = self.typescript.clone();
@@ -477,22 +623,27 @@ impl<'a> Context<'a> {
// Emit all the JS for importing all our functionality
assert!(
!self.config.mode.uses_es_modules() || js.is_empty(),
- "ES modules require imports to be at the start of the file"
+ "ES modules require imports to be at the start of the file, but we \
+ generated some JS before the imports: {}",
+ js
);
- js.push_str(&imports);
- js.push_str("\n");
- js.push_str(&self.imports_post);
- js.push_str("\n");
+
+ let mut push_with_newline = |s| {
+ js.push_str(s);
+ if !s.is_empty() {
+ js.push('\n');
+ }
+ };
+
+ push_with_newline(&imports);
+ push_with_newline(&self.imports_post);
// Emit all our exports from this module
- js.push_str(&self.globals);
- js.push_str("\n");
+ push_with_newline(&self.globals);
// Generate the initialization glue, if there was any
- js.push_str(&init_js);
- js.push_str("\n");
- js.push_str(&footer);
- js.push_str("\n");
+ push_with_newline(&init_js);
+ push_with_newline(&footer);
if self.config.mode.no_modules() {
js.push_str("})();\n");
}
@@ -513,7 +664,7 @@ impl<'a> Context<'a> {
match &self.config.mode {
OutputMode::NoModules { .. } => {
- for (module, _items) in self.js_imports.iter() {
+ if let Some((module, _items)) = self.js_imports.iter().next() {
bail!(
"importing from `{}` isn't supported with `--target no-modules`",
module
@@ -521,9 +672,7 @@ impl<'a> Context<'a> {
}
}
- OutputMode::Node {
- experimental_modules: false,
- } => {
+ OutputMode::Node { module: false } => {
for (module, items) in crate::sorted_iter(&self.js_imports) {
imports.push_str("const { ");
for (i, (item, rename)) in items.iter().enumerate() {
@@ -536,7 +685,7 @@ impl<'a> Context<'a> {
imports.push_str(other)
}
}
- if module.starts_with(".") || PathBuf::from(module).is_absolute() {
+ if module.starts_with('.') || PathBuf::from(module).is_absolute() {
imports.push_str(" } = require(String.raw`");
} else {
imports.push_str(" } = require(`");
@@ -547,9 +696,7 @@ impl<'a> Context<'a> {
}
OutputMode::Bundler { .. }
- | OutputMode::Node {
- experimental_modules: true,
- }
+ | OutputMode::Node { module: true }
| OutputMode::Web
| OutputMode::Deno => {
for (module, items) in crate::sorted_iter(&self.js_imports) {
@@ -578,28 +725,53 @@ impl<'a> Context<'a> {
has_memory: bool,
has_module_or_path_optional: bool,
) -> Result {
- let output = crate::wasm2es6js::interface(&self.module)?;
+ let output = crate::wasm2es6js::interface(self.module)?;
let (memory_doc, memory_param) = if has_memory {
(
- "* @param {WebAssembly.Memory} maybe_memory\n",
- ", maybe_memory?: WebAssembly.Memory",
+ "* @param {WebAssembly.Memory} memory - Deprecated.\n",
+ ", memory?: WebAssembly.Memory",
)
} else {
("", "")
};
+ let stack_size = if self.threads_enabled {
+ ", thread_stack_size?: number"
+ } else {
+ ""
+ };
let arg_optional = if has_module_or_path_optional { "?" } else { "" };
// With TypeScript 3.8.3, I'm seeing that any "export"s at the root level cause TypeScript to ignore all "declare" statements.
// So using "declare" everywhere for at least the NoModules option.
// Also in (at least) the NoModules, the `init()` method is renamed to `wasm_bindgen()`.
let setup_function_declaration;
+ let mut sync_init_function = String::new();
let declare_or_export;
if self.config.mode.no_modules() {
declare_or_export = "declare";
setup_function_declaration = "declare function wasm_bindgen";
} else {
declare_or_export = "export";
- setup_function_declaration = "export default function init";
+
+ sync_init_function.push_str(&format!(
+ "\
+ {declare_or_export} type SyncInitInput = BufferSource | WebAssembly.Module;\n\
+ /**\n\
+ * Instantiates the given `module`, which can either be bytes or\n\
+ * a precompiled `WebAssembly.Module`.\n\
+ *\n\
+ * @param {{{{ module: SyncInitInput{memory_param}{stack_size} }}}} module - Passing `SyncInitInput` directly is deprecated.\n\
+ {memory_doc}\
+ *\n\
+ * @returns {{InitOutput}}\n\
+ */\n\
+ export function initSync(module: {{ module: SyncInitInput{memory_param}{stack_size} }} | SyncInitInput{memory_param}): InitOutput;\n\n\
+ ",
+ memory_doc = memory_doc,
+ memory_param = memory_param
+ ));
+
+ setup_function_declaration = "export default function __wbg_init";
}
Ok(format!(
"\n\
@@ -608,19 +780,21 @@ impl<'a> Context<'a> {
{declare_or_export} interface InitOutput {{\n\
{output}}}\n\
\n\
+ {sync_init_function}\
/**\n\
* If `module_or_path` is {{RequestInfo}} or {{URL}}, makes a request and\n\
* for everything else, calls `WebAssembly.instantiate` directly.\n\
*\n\
- * @param {{InitInput | Promise}} module_or_path\n\
+ * @param {{{{ module_or_path: InitInput | Promise{memory_param}{stack_size} }}}} module_or_path - Passing `InitInput` directly is deprecated.\n\
{}\
*\n\
* @returns {{Promise}}\n\
*/\n\
{setup_function_declaration} \
- (module_or_path{}: InitInput | Promise{}): Promise;\n",
+ (module_or_path{}: {{ module_or_path: InitInput | Promise{memory_param}{stack_size} }} | InitInput | Promise{}): Promise;\n",
memory_doc, arg_optional, memory_param,
output = output,
+ sync_init_function = sync_init_function,
declare_or_export = declare_or_export,
setup_function_declaration = setup_function_declaration,
))
@@ -639,7 +813,7 @@ impl<'a> Context<'a> {
if let Some(id) = mem.import {
self.module.imports.get_mut(id).module = module_name.to_string();
init_memory = format!(
- "imports.{}.memory = maybe_memory || new WebAssembly.Memory({{",
+ "imports.{}.memory = memory || new WebAssembly.Memory({{",
module_name
);
init_memory.push_str(&format!("initial:{}", mem.initial));
@@ -650,7 +824,7 @@ impl<'a> Context<'a> {
init_memory.push_str(",shared:true");
}
init_memory.push_str("});");
- init_memory_arg = ", maybe_memory";
+ init_memory_arg = ", memory";
has_memory = true;
}
}
@@ -659,20 +833,14 @@ impl<'a> Context<'a> {
match self.config.mode {
OutputMode::Web => format!(
"\
- if (typeof input === 'undefined') {{
- input = new URL('{stem}_bg.wasm', import.meta.url);
+ if (typeof module_or_path === 'undefined') {{
+ module_or_path = new URL('{stem}_bg.wasm', import.meta.url);
}}",
stem = self.config.stem()?
),
OutputMode::NoModules { .. } => "\
- if (typeof input === 'undefined') {
- let src;
- if (typeof document === 'undefined') {
- src = location.href;
- } else {
- src = document.currentScript.src;
- }
- input = src.replace(/\\.js$/, '_bg.wasm');
+ if (typeof module_or_path === 'undefined' && typeof script_src !== 'undefined') {
+ module_or_path = script_src.replace(/\\.js$/, '_bg.wasm');
}"
.to_string(),
_ => "".to_string(),
@@ -689,17 +857,17 @@ impl<'a> Context<'a> {
// Initialize the `imports` object for all import definitions that we're
// directed to wire up.
let mut imports_init = String::new();
- if self.wasm_import_definitions.len() > 0 {
- imports_init.push_str("imports.");
- imports_init.push_str(module_name);
- imports_init.push_str(" = {};\n");
- }
- for (id, js) in crate::sorted_iter(&self.wasm_import_definitions) {
+
+ imports_init.push_str("imports.");
+ imports_init.push_str(module_name);
+ imports_init.push_str(" = {};\n");
+
+ for (id, js) in iter_by_import(&self.wasm_import_definitions, self.module) {
let import = self.module.imports.get_mut(*id);
import.module = module_name.to_string();
imports_init.push_str("imports.");
imports_init.push_str(module_name);
- imports_init.push_str(".");
+ imports_init.push('.');
imports_init.push_str(&import.name);
imports_init.push_str(" = ");
imports_init.push_str(js.trim());
@@ -714,10 +882,7 @@ impl<'a> Context<'a> {
.filter(|i| {
// Importing memory is handled specially in this area, so don't
// consider this a candidate for importing from extra modules.
- match i.kind {
- walrus::ImportKind::Memory(_) => false,
- _ => true,
- }
+ !(matches!(i.kind, walrus::ImportKind::Memory(_)))
})
.map(|i| &i.module)
.collect::>();
@@ -733,9 +898,25 @@ impl<'a> Context<'a> {
imports_init.push_str(&format!("imports['{}'] = __wbg_star{};\n", extra, i));
}
+ let mut init_memviews = String::new();
+ for &(num, ref views) in self.memories.values() {
+ for kind in views {
+ writeln!(
+ init_memviews,
+ // Reset the memory views to null in case `init` gets called multiple times.
+ // Without this, the `length = 0` check would never detect that the view was
+ // outdated.
+ "cached{kind}Memory{num} = null;",
+ kind = kind,
+ num = num,
+ )
+ .unwrap()
+ }
+ }
+
let js = format!(
"\
- async function load(module, imports) {{
+ async function __wbg_load(module, imports) {{
if (typeof Response === 'function' && module instanceof Response) {{
if (typeof WebAssembly.instantiateStreaming === 'function') {{
try {{
@@ -744,7 +925,7 @@ impl<'a> Context<'a> {
}} catch (e) {{
if (module.headers.get('Content-Type') != 'application/wasm') {{
console.warn(\"`WebAssembly.instantiateStreaming` failed \
- because your server does not serve wasm with \
+ because your server does not serve Wasm with \
`application/wasm` MIME type. Falling back to \
`WebAssembly.instantiate` which is slower. Original \
error:\\n\", e);
@@ -770,34 +951,106 @@ impl<'a> Context<'a> {
}}
}}
- async function init(input{init_memory_arg}) {{
- {default_module_path}
+ function __wbg_get_imports() {{
const imports = {{}};
{imports_init}
+ return imports;
+ }}
- if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {{
- input = fetch(input);
- }}
-
+ function __wbg_init_memory(imports, memory) {{
{init_memory}
+ }}
- const {{ instance, module }} = await load(await input, imports);
-
+ function __wbg_finalize_init(instance, module{init_stack_size_arg}) {{
wasm = instance.exports;
- init.__wbindgen_wasm_module = module;
+ __wbg_init.__wbindgen_wasm_module = module;
+ {init_memviews}
+ {init_stack_size_check}
{start}
return wasm;
}}
+
+ function initSync(module{init_memory_arg}) {{
+ if (wasm !== undefined) return wasm;
+
+ {init_stack_size}
+ if (typeof module !== 'undefined') {{
+ if (Object.getPrototypeOf(module) === Object.prototype) {{
+ ({{module{init_memory_arg}{init_stack_size_arg}}} = module)
+ }} else {{
+ console.warn('using deprecated parameters for `initSync()`; pass a single object instead')
+ }}
+ }}
+
+ const imports = __wbg_get_imports();
+
+ __wbg_init_memory(imports{init_memory_arg});
+
+ if (!(module instanceof WebAssembly.Module)) {{
+ module = new WebAssembly.Module(module);
+ }}
+
+ const instance = new WebAssembly.Instance(module, imports);
+
+ return __wbg_finalize_init(instance, module{init_stack_size_arg});
+ }}
+
+ async function __wbg_init(module_or_path{init_memory_arg}) {{
+ if (wasm !== undefined) return wasm;
+
+ {init_stack_size}
+ if (typeof module_or_path !== 'undefined') {{
+ if (Object.getPrototypeOf(module_or_path) === Object.prototype) {{
+ ({{module_or_path{init_memory_arg}{init_stack_size_arg}}} = module_or_path)
+ }} else {{
+ console.warn('using deprecated parameters for the initialization function; pass a single object instead')
+ }}
+ }}
+
+ {default_module_path}
+ const imports = __wbg_get_imports();
+
+ if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) {{
+ module_or_path = fetch(module_or_path);
+ }}
+
+ __wbg_init_memory(imports{init_memory_arg});
+
+ const {{ instance, module }} = await __wbg_load(await module_or_path, imports);
+
+ return __wbg_finalize_init(instance, module{init_stack_size_arg});
+ }}
",
init_memory_arg = init_memory_arg,
default_module_path = default_module_path,
init_memory = init_memory,
- start = if needs_manual_start {
+ init_memviews = init_memviews,
+ start = if needs_manual_start && self.threads_enabled {
+ "wasm.__wbindgen_start(thread_stack_size);"
+ } else if needs_manual_start {
"wasm.__wbindgen_start();"
} else {
""
},
imports_init = imports_init,
+ init_stack_size = if self.threads_enabled {
+ "let thread_stack_size"
+ } else {
+ ""
+ },
+ init_stack_size_arg = if self.threads_enabled {
+ ", thread_stack_size"
+ } else {
+ ""
+ },
+ init_stack_size_check = if self.threads_enabled {
+ format!(
+ "if (typeof thread_stack_size !== 'undefined' && (typeof thread_stack_size !== 'number' || thread_stack_size === 0 || thread_stack_size % {} !== 0)) {{ throw 'invalid stack size' }}",
+ wasm_bindgen_threads_xform::PAGE_SIZE,
+ )
+ } else {
+ String::new()
+ },
);
Ok((js, ts))
@@ -814,43 +1067,57 @@ impl<'a> Context<'a> {
let mut dst = format!("class {} {{\n", name);
let mut ts_dst = format!("export {}", dst);
- if self.config.debug && !class.has_constructor {
- dst.push_str(
- "
- constructor() {
- throw new Error('cannot invoke `new` directly');
- }
- ",
- );
+ if !class.has_constructor {
+ // declare the constructor as private to prevent direct instantiation
+ ts_dst.push_str(" private constructor();\n");
+
+ if self.config.debug {
+ dst.push_str(
+ "
+ constructor() {
+ throw new Error('cannot invoke `new` directly');
+ }
+ ",
+ );
+ }
}
if class.wrap_needed {
dst.push_str(&format!(
"
static __wrap(ptr) {{
- const obj = Object.create({}.prototype);
- obj.ptr = ptr;
- {}
+ ptr = ptr >>> 0;
+ const obj = Object.create({name}.prototype);
+ obj.__wbg_ptr = ptr;
+ {name}Finalization.register(obj, obj.__wbg_ptr, obj);
return obj;
}}
- ",
- name,
- if self.config.weak_refs {
- format!("{}Finalization.register(obj, obj.ptr, obj);", name)
- } else {
- String::new()
- },
+ "
));
}
- if self.config.weak_refs {
- self.global(&format!(
- "const {}Finalization = new FinalizationRegistry(ptr => wasm.{}(ptr));",
+ if class.unwrap_needed {
+ dst.push_str(&format!(
+ "
+ static __unwrap(jsValue) {{
+ if (!(jsValue instanceof {})) {{
+ return 0;
+ }}
+ return jsValue.__destroy_into_raw();
+ }}
+ ",
name,
- wasm_bindgen_shared::free_function(&name),
));
}
+ self.global(&format!(
+ "
+ const {name}Finalization = (typeof FinalizationRegistry === 'undefined')
+ ? {{ register: () => {{}}, unregister: () => {{}} }}
+ : new FinalizationRegistry(ptr => wasm.{}(ptr >>> 0, 1));",
+ wasm_bindgen_shared::free_function(name),
+ ));
+
// If the class is inspectable, generate `toJSON` and `toString`
// to expose all readable properties of the class. Otherwise,
// the class shows only the "ptr" property when logged or serialized
@@ -874,6 +1141,16 @@ impl<'a> Context<'a> {
format!("{}{name}: this.{name},\n", fields, name = field_name)
})
));
+ // Also add definitions to the .d.ts file.
+ ts_dst.push_str(
+ "\
+ /**\n*\
+ * Return copy of self without private attributes.\n\
+ */\n toJSON(): Object;\n\
+ /**\n\
+ * Return stringified version of self.\n\
+ */\n toString(): string;\n",
+ );
if self.config.mode.nodejs() {
// `util.inspect` must be imported in Node.js to define [inspect.custom]
@@ -902,56 +1179,166 @@ impl<'a> Context<'a> {
dst.push_str(&format!(
"
__destroy_into_raw() {{
- const ptr = this.ptr;
- this.ptr = 0;
- {}
+ const ptr = this.__wbg_ptr;
+ this.__wbg_ptr = 0;
+ {name}Finalization.unregister(this);
return ptr;
}}
free() {{
const ptr = this.__destroy_into_raw();
- wasm.{}(ptr);
+ wasm.{}(ptr, 0);
}}
",
- if self.config.weak_refs {
- format!("{}Finalization.unregister(this);", name)
- } else {
- String::new()
- },
- wasm_bindgen_shared::free_function(&name),
+ wasm_bindgen_shared::free_function(name),
));
ts_dst.push_str(" free(): void;\n");
+ if self.config.symbol_dispose {
+ dst.push_str(
+ "
+ [Symbol.dispose]() {{
+ this.free();
+ }}
+ ",
+ );
+ ts_dst.push_str(" [Symbol.dispose](): void;\n");
+ }
dst.push_str(&class.contents);
ts_dst.push_str(&class.typescript);
- let mut fields = class.typescript_fields.keys().collect::>();
- fields.sort(); // make sure we have deterministic output
- for name in fields {
- let (ty, docs, has_setter, is_optional) = &class.typescript_fields[name];
- ts_dst.push_str(docs);
- ts_dst.push_str(" ");
- if !has_setter {
- ts_dst.push_str("readonly ");
- }
- ts_dst.push_str(name);
- if *is_optional {
- ts_dst.push_str("?: ");
- } else {
- ts_dst.push_str(": ");
- }
- ts_dst.push_str(&ty);
- ts_dst.push_str(";\n");
- }
- dst.push_str("}\n");
+ self.write_class_field_types(class, &mut ts_dst);
+
+ dst.push('}');
ts_dst.push_str("}\n");
- self.export(&name, &dst, Some(&class.comments))?;
- self.typescript.push_str(&class.comments);
- self.typescript.push_str(&ts_dst);
+ self.export(name, ExportJs::Class(&dst), Some(&class.comments))?;
+
+ if class.generate_typescript {
+ self.typescript.push_str(&class.comments);
+ self.typescript.push_str(&ts_dst);
+ }
Ok(())
}
+ fn write_class_field_types(&mut self, class: &ExportedClass, ts_dst: &mut String) {
+ let mut fields: Vec<&FieldInfo> = class.typescript_fields.values().collect();
+ fields.sort_by_key(|f| f.order); // make sure we have deterministic output
+
+ for FieldInfo {
+ name,
+ is_static,
+ getter,
+ setter,
+ ..
+ } in fields
+ {
+ let is_static = if *is_static { "static " } else { "" };
+
+ let write_docs = |ts_dst: &mut String, docs: &str| {
+ if docs.is_empty() {
+ return;
+ }
+ // indent by 2 spaces
+ for line in docs.lines() {
+ ts_dst.push_str(" ");
+ ts_dst.push_str(line);
+ ts_dst.push('\n');
+ }
+ };
+ let write_getter = |ts_dst: &mut String, getter: &FieldAccessor| {
+ write_docs(ts_dst, &getter.docs);
+ ts_dst.push_str(" ");
+ ts_dst.push_str(is_static);
+ ts_dst.push_str("get ");
+ ts_dst.push_str(name);
+ ts_dst.push_str("(): ");
+ ts_dst.push_str(&getter.ty);
+ ts_dst.push_str(";\n");
+ };
+ let write_setter = |ts_dst: &mut String, setter: &FieldAccessor| {
+ write_docs(ts_dst, &setter.docs);
+ ts_dst.push_str(" ");
+ ts_dst.push_str(is_static);
+ ts_dst.push_str("set ");
+ ts_dst.push_str(name);
+ ts_dst.push_str("(value: ");
+ ts_dst.push_str(&setter.ty);
+ if setter.is_optional {
+ ts_dst.push_str(" | undefined");
+ }
+ ts_dst.push_str(");\n");
+ };
+
+ match (getter, setter) {
+ (None, None) => unreachable!("field without getter or setter"),
+ (Some(getter), None) => {
+ // readonly property
+ write_docs(ts_dst, &getter.docs);
+ ts_dst.push_str(" ");
+ ts_dst.push_str(is_static);
+ ts_dst.push_str("readonly ");
+ ts_dst.push_str(name);
+ ts_dst.push_str(if getter.is_optional { "?: " } else { ": " });
+ ts_dst.push_str(&getter.ty);
+ ts_dst.push_str(";\n");
+ }
+ (None, Some(setter)) => {
+ // write-only property
+
+ // Note: TypeScript does not handle the types of write-only
+ // properties correctly and will allow reads from write-only
+ // properties. This isn't a wasm-bindgen issue, but a
+ // TypeScript issue.
+ write_setter(ts_dst, setter);
+ }
+ (Some(getter), Some(setter)) => {
+ // read-write property
+
+ // Here's the tricky part. The getter and setter might have
+ // different types. Obviously, we can only declare a
+ // property as `foo: T` if both the getter and setter have
+ // the same type `T`. If they don't, we have to declare the
+ // getter and setter separately.
+
+ // We current generate types for optional arguments and
+ // return values differently. This is why for the field
+ // `foo: Option`, the setter will have type `T` with
+ // `is_optional` set, while the getter has type
+ // `T | undefined`. Because of this difference, we have to
+ // "normalize" the type of the setter.
+ let same_type = if setter.is_optional {
+ getter.ty == setter.ty.clone() + " | undefined"
+ } else {
+ getter.ty == setter.ty
+ };
+
+ if same_type {
+ // simple property, e.g. foo: T
+
+ // Prefer the docs of the getter over the setter's
+ let docs = if !getter.docs.is_empty() {
+ &getter.docs
+ } else {
+ &setter.docs
+ };
+ write_docs(ts_dst, docs);
+ ts_dst.push_str(" ");
+ ts_dst.push_str(is_static);
+ ts_dst.push_str(name);
+ ts_dst.push_str(if setter.is_optional { "?: " } else { ": " });
+ ts_dst.push_str(&setter.ty);
+ ts_dst.push_str(";\n");
+ } else {
+ // separate getter and setter
+ write_getter(ts_dst, getter);
+ write_setter(ts_dst, setter);
+ }
+ }
+ };
+ }
+ }
+
fn expose_drop_ref(&mut self) {
if !self.should_write_global("drop_ref") {
return;
@@ -1021,28 +1408,41 @@ impl<'a> Context<'a> {
if !self.should_write_global("assert_num") {
return;
}
- self.global(&format!(
+ self.global(
"
- function _assertNum(n) {{
- if (typeof(n) !== 'number') throw new Error('expected a number argument');
- }}
+ function _assertNum(n) {
+ if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`);
+ }
+ ",
+ );
+ }
+
+ fn expose_assert_bigint(&mut self) {
+ if !self.should_write_global("assert_bigint") {
+ return;
+ }
+ self.global(
"
- ));
+ function _assertBigInt(n) {
+ if (typeof(n) !== 'bigint') throw new Error(`expected a bigint argument, found ${typeof(n)}`);
+ }
+ ",
+ );
}
fn expose_assert_bool(&mut self) {
if !self.should_write_global("assert_bool") {
return;
}
- self.global(&format!(
- "
- function _assertBoolean(n) {{
- if (typeof(n) !== 'boolean') {{
- throw new Error('expected a boolean argument');
- }}
- }}
+ self.global(
"
- ));
+ function _assertBoolean(n) {
+ if (typeof(n) !== 'boolean') {
+ throw new Error(`expected a boolean argument, found ${typeof(n)}`);
+ }
+ }
+ ",
+ );
}
fn expose_wasm_vector_len(&mut self) {
@@ -1057,7 +1457,7 @@ impl<'a> Context<'a> {
let debug = if self.config.debug {
"
- if (typeof(arg) !== 'string') throw new Error('expected a string argument');
+ if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`);
"
} else {
""
@@ -1065,7 +1465,7 @@ impl<'a> Context<'a> {
let mem = self.expose_uint8_memory(memory);
let ret = MemView {
- name: "passStringToWasm",
+ name: "passStringToWasm".into(),
num: mem.num,
};
if !self.should_write_global(ret.to_string()) {
@@ -1086,7 +1486,7 @@ impl<'a> Context<'a> {
// Another possibility is to use `TextEncoder#encodeInto` which is much
// newer and isn't implemented everywhere yet. It's more efficient,
- // however, becaues it allows us to elide an intermediate allocation.
+ // however, because it allows us to elide an intermediate allocation.
let encode_into = "function (arg, view) {
return cachedTextEncoder.encodeInto(arg, view);
}";
@@ -1125,7 +1525,7 @@ impl<'a> Context<'a> {
}
}
- // A fast path that directly writes char codes into WASM memory as long
+ // A fast path that directly writes char codes into Wasm memory as long
// as it finds only ASCII characters.
//
// This is much faster for common ASCII strings because it can avoid
@@ -1138,14 +1538,14 @@ impl<'a> Context<'a> {
"\
if (realloc === undefined) {{
const buf = cachedTextEncoder.encode(arg);
- const ptr = malloc(buf.length);
+ const ptr = malloc(buf.length, 1) >>> 0;
{mem}().subarray(ptr, ptr + buf.length).set(buf);
WASM_VECTOR_LEN = buf.length;
return ptr;
}}
let len = arg.length;
- let ptr = malloc(len);
+ let ptr = malloc(len, 1) >>> 0;
const mem = {mem}();
@@ -1160,11 +1560,6 @@ impl<'a> Context<'a> {
mem = mem,
);
- // TODO:
- // When converting a JS string to UTF-8, the maximum size is `arg.length * 3`,
- // so we just allocate that. This wastes memory, so we should investigate
- // looping over the string to calculate the precise size, or perhaps using
- // `shrink_to_fit` on the Rust side.
self.global(&format!(
"function {name}(arg, malloc, realloc) {{
{debug}
@@ -1173,11 +1568,12 @@ impl<'a> Context<'a> {
if (offset !== 0) {{
arg = arg.slice(offset);
}}
- ptr = realloc(ptr, len, len = offset + arg.length * 3);
+ ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
const view = {mem}().subarray(ptr + offset, ptr + len);
const ret = encodeString(arg, view);
{debug_end}
offset += ret.written;
+ ptr = realloc(ptr, len, offset, 1) >>> 0;
}}
WASM_VECTOR_LEN = offset;
@@ -1228,9 +1624,9 @@ impl<'a> Context<'a> {
}
fn expose_pass_array_jsvalue_to_wasm(&mut self, memory: MemoryId) -> Result {
- let mem = self.expose_uint32_memory(memory);
+ let mem = self.expose_dataview_memory(memory);
let ret = MemView {
- name: "passArrayJsValueToWasm",
+ name: "passArrayJsValueToWasm".into(),
num: mem.num,
};
if !self.should_write_global(ret.to_string()) {
@@ -1244,34 +1640,32 @@ impl<'a> Context<'a> {
let add = self.expose_add_to_externref_table(table, alloc)?;
self.global(&format!(
"
- function {}(array, malloc) {{
- const ptr = malloc(array.length * 4);
- const mem = {}();
+ function {ret}(array, malloc) {{
+ const ptr = malloc(array.length * 4, 4) >>> 0;
for (let i = 0; i < array.length; i++) {{
- mem[ptr / 4 + i] = {}(array[i]);
+ const add = {add}(array[i]);
+ {mem}().setUint32(ptr + 4 * i, add, true);
}}
WASM_VECTOR_LEN = array.length;
return ptr;
}}
",
- ret, mem, add,
));
}
_ => {
self.expose_add_heap_object();
self.global(&format!(
"
- function {}(array, malloc) {{
- const ptr = malloc(array.length * 4);
- const mem = {}();
+ function {ret}(array, malloc) {{
+ const ptr = malloc(array.length * 4, 4) >>> 0;
+ const mem = {mem}();
for (let i = 0; i < array.length; i++) {{
- mem[ptr / 4 + i] = addHeapObject(array[i]);
+ mem.setUint32(ptr + 4 * i, addHeapObject(array[i]), true);
}}
WASM_VECTOR_LEN = array.length;
return ptr;
}}
",
- ret, mem,
));
}
}
@@ -1285,7 +1679,7 @@ impl<'a> Context<'a> {
size: usize,
) -> Result {
let ret = MemView {
- name,
+ name: name.into(),
num: view.num,
};
if !self.should_write_global(ret.to_string()) {
@@ -1295,7 +1689,7 @@ impl<'a> Context<'a> {
self.global(&format!(
"
function {}(arg, malloc) {{
- const ptr = malloc(arg.length * {size});
+ const ptr = malloc(arg.length * {size}, {size}) >>> 0;
{}().set(arg, ptr / {size});
WASM_VECTOR_LEN = arg.length;
return ptr;
@@ -1308,11 +1702,19 @@ impl<'a> Context<'a> {
Ok(ret)
}
+ fn expose_symbol_dispose(&mut self) -> Result<(), Error> {
+ if !self.should_write_global("symbol_dispose") {
+ return Ok(());
+ }
+ self.global("if(!Symbol.dispose) { Symbol.dispose = Symbol('Symbol.dispose'); }");
+ Ok(())
+ }
+
fn expose_text_encoder(&mut self) -> Result<(), Error> {
if !self.should_write_global("text_encoder") {
return Ok(());
}
- self.expose_text_processor("TextEncoder", "('utf-8')")
+ self.expose_text_processor("TextEncoder", "encode", "('utf-8')", None)
}
fn expose_text_decoder(&mut self) -> Result<(), Error> {
@@ -1320,18 +1722,29 @@ impl<'a> Context<'a> {
return Ok(());
}
- // `ignoreBOM` is needed so that the BOM will be preserved when sending a string from Rust to JS
- // `fatal` is needed to catch any weird encoding bugs when sending a string from Rust to JS
- self.expose_text_processor("TextDecoder", "('utf-8', { ignoreBOM: true, fatal: true })")?;
-
// This is needed to workaround a bug in Safari
// See: https://github.com/rustwasm/wasm-bindgen/issues/1825
- self.global("cachedTextDecoder.decode();");
+ let init = Some("cachedTextDecoder.decode();");
+
+ // `ignoreBOM` is needed so that the BOM will be preserved when sending a string from Rust to JS
+ // `fatal` is needed to catch any weird encoding bugs when sending a string from Rust to JS
+ self.expose_text_processor(
+ "TextDecoder",
+ "decode",
+ "('utf-8', { ignoreBOM: true, fatal: true })",
+ init,
+ )?;
Ok(())
}
- fn expose_text_processor(&mut self, s: &str, args: &str) -> Result<(), Error> {
+ fn expose_text_processor(
+ &mut self,
+ s: &str,
+ op: &str,
+ args: &str,
+ init: Option<&str>,
+ ) -> Result<(), Error> {
match &self.config.mode {
OutputMode::Node { .. } => {
let name = self.import_name(&JsImport {
@@ -1359,10 +1772,26 @@ impl<'a> Context<'a> {
| OutputMode::Web
| OutputMode::NoModules { .. }
| OutputMode::Bundler { browser_only: true } => {
- self.global(&format!("const cached{0} = new {0}{1};", s, args))
+ self.global(&format!("const cached{0} = (typeof {0} !== 'undefined' ? new {0}{1} : {{ {2}: () => {{ throw Error('{0} not available') }} }} );", s, args, op))
}
};
+ if let Some(init) = init {
+ match &self.config.mode {
+ OutputMode::Node { .. }
+ | OutputMode::Bundler {
+ browser_only: false,
+ } => self.global(init),
+ OutputMode::Deno
+ | OutputMode::Web
+ | OutputMode::NoModules { .. }
+ | OutputMode::Bundler { browser_only: true } => self.global(&format!(
+ "if (typeof {} !== 'undefined') {{ {} }};",
+ s, init
+ )),
+ }
+ }
+
Ok(())
}
@@ -1370,7 +1799,7 @@ impl<'a> Context<'a> {
self.expose_text_decoder()?;
let mem = self.expose_uint8_memory(memory);
let ret = MemView {
- name: "getStringFromWasm",
+ name: "getStringFromWasm".into(),
num: mem.num,
};
@@ -1392,6 +1821,7 @@ impl<'a> Context<'a> {
self.global(&format!(
"
function {}(ptr, len) {{
+ ptr = ptr >>> 0;
return cachedTextDecoder.decode({}().{}(ptr, ptr + len));
}}
",
@@ -1400,12 +1830,21 @@ impl<'a> Context<'a> {
Ok(ret)
}
- fn expose_get_cached_string_from_wasm(&mut self, memory: MemoryId) -> Result {
- self.expose_get_object();
- let get = self.expose_get_string_from_wasm(memory)?;
+ fn expose_get_cached_string_from_wasm(
+ &mut self,
+ memory: MemoryId,
+ table: Option,
+ ) -> Result {
+ let get_object = if let Some(table) = table {
+ self.expose_get_from_externref_table(table)?.to_string()
+ } else {
+ self.expose_get_object();
+ "getObject".to_string()
+ };
+ let get_string = self.expose_get_string_from_wasm(memory)?;
let ret = MemView {
- name: "getCachedStringFromWasm",
- num: get.num,
+ name: "getCachedStringFromWasm".into(),
+ num: get_string.num,
};
if !self.should_write_global(ret.to_string()) {
@@ -1422,23 +1861,25 @@ impl<'a> Context<'a> {
// the fact that `getObject(0)` is guaranteed to be `undefined`.
self.global(&format!(
"
- function {}(ptr, len) {{
+ function {name}(ptr, len) {{
if (ptr === 0) {{
- return getObject(len);
+ return {get_object}(len);
}} else {{
- return {}(ptr, len);
+ return {get_string}(ptr, len);
}}
}}
",
- ret, get,
+ name = ret,
+ get_string = get_string,
+ get_object = get_object
));
Ok(ret)
}
fn expose_get_array_js_value_from_wasm(&mut self, memory: MemoryId) -> Result {
- let mem = self.expose_uint32_memory(memory);
+ let mem = self.expose_dataview_memory(memory);
let ret = MemView {
- name: "getArrayJsValueFromWasm",
+ name: "getArrayJsValueFromWasm".into(),
num: mem.num,
};
if !self.should_write_global(ret.to_string()) {
@@ -1451,11 +1892,11 @@ impl<'a> Context<'a> {
self.global(&format!(
"
function {}(ptr, len) {{
+ ptr = ptr >>> 0;
const mem = {}();
- const slice = mem.subarray(ptr / 4, ptr / 4 + len);
const result = [];
- for (let i = 0; i < slice.length; i++) {{
- result.push(wasm.{}.get(slice[i]));
+ for (let i = ptr; i < ptr + 4 * len; i += 4) {{
+ result.push(wasm.{}.get(mem.getUint32(i, true)));
}}
wasm.{}(ptr, len);
return result;
@@ -1469,11 +1910,11 @@ impl<'a> Context<'a> {
self.global(&format!(
"
function {}(ptr, len) {{
+ ptr = ptr >>> 0;
const mem = {}();
- const slice = mem.subarray(ptr / 4, ptr / 4 + len);
const result = [];
- for (let i = 0; i < slice.length; i++) {{
- result.push(takeObject(slice[i]));
+ for (let i = ptr; i < ptr + 4 * len; i += 4) {{
+ result.push(takeObject(mem.getUint32(i, true)));
}}
return result;
}}
@@ -1542,7 +1983,7 @@ impl<'a> Context<'a> {
fn arrayget(&mut self, name: &'static str, view: MemView, size: usize) -> MemView {
let ret = MemView {
- name,
+ name: name.into(),
num: view.num,
};
if !self.should_write_global(name) {
@@ -1551,6 +1992,7 @@ impl<'a> Context<'a> {
self.global(&format!(
"
function {name}(ptr, len) {{
+ ptr = ptr >>> 0;
return {mem}().subarray(ptr / {size}, ptr / {size} + len);
}}
",
@@ -1558,105 +2000,125 @@ impl<'a> Context<'a> {
mem = view,
size = size,
));
- return ret;
+ ret
}
fn expose_int8_memory(&mut self, memory: MemoryId) -> MemView {
- self.memview("getInt8Memory", "new Int8Array", memory)
+ self.memview("Int8Array", memory)
}
fn expose_uint8_memory(&mut self, memory: MemoryId) -> MemView {
- self.memview("getUint8Memory", "new Uint8Array", memory)
+ self.memview("Uint8Array", memory)
}
fn expose_clamped_uint8_memory(&mut self, memory: MemoryId) -> MemView {
- self.memview("getUint8ClampedMemory", "new Uint8ClampedArray", memory)
+ self.memview("Uint8ClampedArray", memory)
}
fn expose_int16_memory(&mut self, memory: MemoryId) -> MemView {
- self.memview("getInt16Memory", "new Int16Array", memory)
+ self.memview("Int16Array", memory)
}
fn expose_uint16_memory(&mut self, memory: MemoryId) -> MemView {
- self.memview("getUint16Memory", "new Uint16Array", memory)
+ self.memview("Uint16Array", memory)
}
fn expose_int32_memory(&mut self, memory: MemoryId) -> MemView {
- self.memview("getInt32Memory", "new Int32Array", memory)
+ self.memview("Int32Array", memory)
}
fn expose_uint32_memory(&mut self, memory: MemoryId) -> MemView {
- self.memview("getUint32Memory", "new Uint32Array", memory)
+ self.memview("Uint32Array", memory)
}
fn expose_int64_memory(&mut self, memory: MemoryId) -> MemView {
- self.memview("getInt64Memory", "new BigInt64Array", memory)
+ self.memview("BigInt64Array", memory)
}
fn expose_uint64_memory(&mut self, memory: MemoryId) -> MemView {
- self.memview("getUint64Memory", "new BigUint64Array", memory)
+ self.memview("BigUint64Array", memory)
}
fn expose_f32_memory(&mut self, memory: MemoryId) -> MemView {
- self.memview("getFloat32Memory", "new Float32Array", memory)
+ self.memview("Float32Array", memory)
}
fn expose_f64_memory(&mut self, memory: MemoryId) -> MemView {
- self.memview("getFloat64Memory", "new Float64Array", memory)
+ self.memview("Float64Array", memory)
}
- fn memview_function(&mut self, t: VectorKind, memory: MemoryId) -> MemView {
- match t {
- VectorKind::String => self.expose_uint8_memory(memory),
- VectorKind::I8 => self.expose_int8_memory(memory),
- VectorKind::U8 => self.expose_uint8_memory(memory),
- VectorKind::ClampedU8 => self.expose_clamped_uint8_memory(memory),
- VectorKind::I16 => self.expose_int16_memory(memory),
- VectorKind::U16 => self.expose_uint16_memory(memory),
- VectorKind::I32 => self.expose_int32_memory(memory),
- VectorKind::U32 => self.expose_uint32_memory(memory),
- VectorKind::I64 => self.expose_int64_memory(memory),
- VectorKind::U64 => self.expose_uint64_memory(memory),
- VectorKind::F32 => self.expose_f32_memory(memory),
- VectorKind::F64 => self.expose_f64_memory(memory),
- VectorKind::Externref => self.expose_uint32_memory(memory),
- VectorKind::NamedExternref(_) => self.expose_uint32_memory(memory),
- }
- }
-
- fn memview(&mut self, name: &'static str, js: &str, memory: walrus::MemoryId) -> MemView {
- let view = self.memview_memory(name, memory);
- if !self.should_write_global(name.to_string()) {
+ fn expose_dataview_memory(&mut self, memory: MemoryId) -> MemView {
+ self.memview("DataView", memory)
+ }
+
+ fn memview(&mut self, kind: &'static str, memory: walrus::MemoryId) -> MemView {
+ let view = self.memview_memory(kind, memory);
+ if !self.should_write_global(view.name.clone()) {
return view;
}
let mem = self.export_name_of(memory);
+
+ let cache = format!("cached{}Memory{}", kind, view.num);
+ let resized_check = if self.module.memories.get(memory).shared {
+ // When it's backed by a `SharedArrayBuffer`, growing the Wasm module's memory
+ // doesn't detach old references; instead, it just leaves them pointing to a
+ // slice of the up-to-date memory. So in order to check if it's been grown, we
+ // have to compare it to the up-to-date buffer.
+ format!(
+ "{cache}.buffer !== wasm.{mem}.buffer",
+ cache = cache,
+ mem = mem
+ )
+ } else if kind == "DataView" {
+ // `DataView`s throw when accessing detached memory, including `byteLength`.
+ // However this requires JS engine support, so we fallback to comparing the buffer.
+ format!("{cache}.buffer.detached === true || ({cache}.buffer.detached === undefined && {cache}.buffer !== wasm.{mem}.buffer)", cache = cache)
+ } else {
+ // Otherwise, we can do a quicker check of whether the buffer's been detached,
+ // which is indicated by a length of 0.
+ format!("{cache}.byteLength === 0", cache = cache)
+ };
+
+ self.global(&format!("let {cache} = null;\n"));
+
self.global(&format!(
"
- let cache{name} = null;
function {name}() {{
- if (cache{name} === null || cache{name}.buffer !== wasm.{mem}.buffer) {{
- cache{name} = {js}(wasm.{mem}.buffer);
+ if ({cache} === null || {resized_check}) {{
+ {cache} = new {kind}(wasm.{mem}.buffer);
}}
- return cache{name};
+ return {cache};
}}
",
name = view,
- js = js,
+ cache = cache,
+ resized_check = resized_check,
+ kind = kind,
mem = mem,
));
- return view;
+ view
}
- fn memview_memory(&mut self, name: &'static str, memory: walrus::MemoryId) -> MemView {
- let next = self.memory_indices.len();
- let num = *self.memory_indices.entry(memory).or_insert(next);
- MemView { name, num }
+ fn memview_memory(&mut self, kind: &'static str, memory: walrus::MemoryId) -> MemView {
+ let next = self.memories.len();
+ let &mut (num, ref mut kinds) = self
+ .memories
+ .entry(memory)
+ .or_insert((next, Default::default()));
+ kinds.insert(kind);
+ MemView {
+ name: format!("get{}Memory", kind).into(),
+ num,
+ }
}
fn memview_table(&mut self, name: &'static str, table: walrus::TableId) -> MemView {
let next = self.table_indices.len();
let num = *self.table_indices.entry(table).or_insert(next);
- MemView { name, num }
+ MemView {
+ name: name.into(),
+ num,
+ }
}
fn expose_assert_class(&mut self) {
@@ -1669,7 +2131,6 @@ impl<'a> Context<'a> {
if (!(instance instanceof klass)) {
throw new Error(`expected instance of ${klass.name}`);
}
- return instance.ptr;
}
",
);
@@ -1896,51 +2357,42 @@ impl<'a> Context<'a> {
);
}
- fn expose_u32_cvt_shim(&mut self) -> &'static str {
- let name = "u32CvtShim";
- if !self.should_write_global(name) {
- return name;
- }
- self.global(&format!("const {} = new Uint32Array(2);", name));
- name
- }
-
- fn expose_int64_cvt_shim(&mut self) -> &'static str {
- let name = "int64CvtShim";
- if !self.should_write_global(name) {
- return name;
+ fn expose_is_like_none(&mut self) {
+ if !self.should_write_global("is_like_none") {
+ return;
}
- let n = self.expose_u32_cvt_shim();
- self.global(&format!(
- "const {} = new BigInt64Array({}.buffer);",
- name, n
- ));
- name
+ self.global(
+ "
+ function isLikeNone(x) {
+ return x === undefined || x === null;
+ }
+ ",
+ );
}
- fn expose_uint64_cvt_shim(&mut self) -> &'static str {
- let name = "uint64CvtShim";
- if !self.should_write_global(name) {
- return name;
+ fn expose_assert_non_null(&mut self) {
+ if !self.should_write_global("assert_non_null") {
+ return;
}
- let n = self.expose_u32_cvt_shim();
- self.global(&format!(
- "const {} = new BigUint64Array({}.buffer);",
- name, n
- ));
- name
+ self.global(
+ "
+ function _assertNonNull(n) {
+ if (typeof(n) !== 'number' || n === 0) throw new Error(`expected a number argument that is not 0, found ${n}`);
+ }
+ ",
+ );
}
- fn expose_is_like_none(&mut self) {
- if !self.should_write_global("is_like_none") {
+ fn expose_assert_char(&mut self) {
+ if !self.should_write_global("assert_char") {
return;
}
self.global(
"
- function isLikeNone(x) {
- return x === undefined || x === null;
+ function _assertChar(c) {
+ if (typeof(c) === 'number' && (c >= 0x110000 || (c >= 0xD800 && c < 0xE000))) throw new Error(`expected a valid Unicode scalar value, found ${c}`);
}
- ",
+ ",
);
}
@@ -1951,15 +2403,7 @@ impl<'a> Context<'a> {
let table = self.export_function_table()?;
- let (register, unregister) = if self.config.weak_refs {
- self.expose_closure_finalization()?;
- (
- "CLOSURE_DTORS.register(real, state, state);",
- "CLOSURE_DTORS.unregister(state)",
- )
- } else {
- ("", "")
- };
+ self.expose_closure_finalization()?;
// For mutable closures they can't be invoked recursively.
// To handle that we swap out the `this.a` pointer with zero
@@ -1982,20 +2426,17 @@ impl<'a> Context<'a> {
}} finally {{
if (--state.cnt === 0) {{
wasm.{table}.get(state.dtor)(a, state.b);
- {unregister}
+ CLOSURE_DTORS.unregister(state);
}} else {{
state.a = a;
}}
}}
}};
real.original = state;
- {register}
+ CLOSURE_DTORS.register(real, state, state);
return real;
}}
",
- table = table,
- register = register,
- unregister = unregister,
));
Ok(())
@@ -2008,15 +2449,7 @@ impl<'a> Context<'a> {
let table = self.export_function_table()?;
- let (register, unregister) = if self.config.weak_refs {
- self.expose_closure_finalization()?;
- (
- "CLOSURE_DTORS.register(real, state, state);",
- "CLOSURE_DTORS.unregister(state)",
- )
- } else {
- ("", "")
- };
+ self.expose_closure_finalization()?;
// For shared closures they can be invoked recursively so we
// just immediately pass through `this.a`. If we end up
@@ -2038,18 +2471,15 @@ impl<'a> Context<'a> {
if (--state.cnt === 0) {{
wasm.{table}.get(state.dtor)(state.a, state.b);
state.a = 0;
- {unregister}
+ CLOSURE_DTORS.unregister(state);
}}
}}
}};
real.original = state;
- {register}
+ CLOSURE_DTORS.register(real, state, state);
return real;
}}
",
- table = table,
- register = register,
- unregister = unregister,
));
Ok(())
@@ -2059,15 +2489,15 @@ impl<'a> Context<'a> {
if !self.should_write_global("closure_finalization") {
return Ok(());
}
- assert!(self.config.weak_refs);
let table = self.export_function_table()?;
self.global(&format!(
"
- const CLOSURE_DTORS = new FinalizationRegistry(state => {{
- wasm.{}.get(state.dtor)(state.a, state.b)
- }});
- ",
- table
+ const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined')
+ ? {{ register: () => {{}}, unregister: () => {{}} }}
+ : new FinalizationRegistry(state => {{
+ wasm.{table}.get(state.dtor)(state.a, state.b)
+ }});
+ "
));
Ok(())
@@ -2078,16 +2508,20 @@ impl<'a> Context<'a> {
// Ensure a blank line between adjacent items, and ensure everything is
// terminated with a newline.
while !self.globals.ends_with("\n\n\n") && !self.globals.ends_with("*/\n") {
- self.globals.push_str("\n");
+ self.globals.push('\n');
}
self.globals.push_str(s);
- self.globals.push_str("\n");
+ self.globals.push('\n');
}
fn require_class_wrap(&mut self, name: &str) {
require_class(&mut self.exported_classes, name).wrap_needed = true;
}
+ fn require_class_unwrap(&mut self, name: &str) {
+ require_class(&mut self.exported_classes, name).unwrap_needed = true;
+ }
+
fn add_module_import(&mut self, module: String, name: &str, actual: &str) {
let rename = if name == actual {
None
@@ -2096,7 +2530,7 @@ impl<'a> Context<'a> {
};
self.js_imports
.entry(module)
- .or_insert(Vec::new())
+ .or_default()
.push((name.to_string(), rename));
}
@@ -2104,7 +2538,7 @@ impl<'a> Context<'a> {
if let Some(name) = self.imported_names.get(&import.name) {
let mut name = name.clone();
for field in import.fields.iter() {
- name.push_str(".");
+ name.push('.');
name.push_str(field);
}
return Ok(name.clone());
@@ -2157,7 +2591,7 @@ impl<'a> Context<'a> {
} else {
switch(dst, name, &left[0], &left[1..]);
}
- dst.push_str(")");
+ dst.push(')');
}
format!("l{}", name)
}
@@ -2175,14 +2609,38 @@ impl<'a> Context<'a> {
// After we've got an actual name handle field projections
for field in import.fields.iter() {
- name.push_str(".");
+ name.push('.');
name.push_str(field);
}
Ok(name)
}
+ fn import_static(&mut self, import: &JsImport, optional: bool) -> Result {
+ let mut name = self.import_name(&JsImport {
+ name: import.name.clone(),
+ fields: Vec::new(),
+ })?;
+
+ // After we've got an actual name handle field projections
+ if optional {
+ name = format!("typeof {name} === 'undefined' ? null : {name}");
+
+ for field in import.fields.iter() {
+ name.push_str("?.");
+ name.push_str(field);
+ }
+ } else {
+ for field in import.fields.iter() {
+ name.push('.');
+ name.push_str(field);
+ }
+ }
+
+ Ok(name)
+ }
+
/// If a start function is present, it removes it from the `start` section
- /// of the wasm module and then moves it to an exported function, named
+ /// of the Wasm module and then moves it to an exported function, named
/// `__wbindgen_start`.
fn unstart_start_function(&mut self) -> bool {
let start = match self.module.start.take() {
@@ -2193,6 +2651,22 @@ impl<'a> Context<'a> {
true
}
+ fn expose_get_from_externref_table(&mut self, table: TableId) -> Result {
+ let view = self.memview_table("getFromExternrefTable", table);
+ assert!(self.config.externref);
+ if !self.should_write_global(view.to_string()) {
+ return Ok(view);
+ }
+ let table = self.export_name_of(table);
+ self.global(&format!(
+ "function {view}(idx) {{ return wasm.{table}.get(idx); }}",
+ view = view,
+ table = table,
+ ));
+
+ Ok(view)
+ }
+
fn expose_take_from_externref_table(
&mut self,
table: TableId,
@@ -2249,21 +2723,29 @@ impl<'a> Context<'a> {
pub fn generate(&mut self) -> Result<(), Error> {
self.prestore_global_import_identifiers()?;
- for (id, adapter) in crate::sorted_iter(&self.wit.adapters) {
+ // conditionally override Symbol.dispose
+ if self.config.symbol_dispose && !self.aux.structs.is_empty() {
+ self.expose_symbol_dispose()?;
+ }
+
+ for (id, adapter, kind) in iter_adapeter(self.aux, self.wit, self.module) {
let instrs = match &adapter.kind {
AdapterKind::Import { .. } => continue,
AdapterKind::Local { instructions } => instructions,
};
- self.generate_adapter(*id, adapter, instrs)?;
+ self.generate_adapter(id, adapter, instrs, kind)?;
}
let mut pairs = self.aux.export_map.iter().collect::>();
pairs.sort_by_key(|(k, _)| *k);
check_duplicated_getter_and_setter_names(&pairs)?;
- for e in self.aux.enums.iter() {
+ for (_, e) in crate::sorted_iter(&self.aux.enums) {
self.generate_enum(e)?;
}
+ for (_, e) in crate::sorted_iter(&self.aux.string_enums) {
+ self.generate_string_enum(e)?;
+ }
for s in self.aux.structs.iter() {
self.generate_struct(s)?;
@@ -2309,7 +2791,7 @@ impl<'a> Context<'a> {
| AuxImport::Value(AuxValue::Setter(js, ..))
| AuxImport::ValueWithThis(js, ..)
| AuxImport::Instanceof(js)
- | AuxImport::Static(js)
+ | AuxImport::Static { js, .. }
| AuxImport::StructuralClassGetter(js, ..)
| AuxImport::StructuralClassSetter(js, ..)
| AuxImport::IndexingGetterOfClass(js)
@@ -2329,26 +2811,10 @@ impl<'a> Context<'a> {
id: AdapterId,
adapter: &Adapter,
instrs: &[InstructionData],
+ kind: ContextAdapterKind,
) -> Result<(), Error> {
- enum Kind<'a> {
- Export(&'a AuxExport),
- Import(walrus::ImportId),
- Adapter,
- }
-
- let kind = match self.aux.export_map.get(&id) {
- Some(export) => Kind::Export(export),
- None => {
- let core = self.wit.implements.iter().find(|pair| pair.2 == id);
- match core {
- Some((core, _, _)) => Kind::Import(*core),
- None => Kind::Adapter,
- }
- }
- };
-
let catch = self.aux.imports_with_catch.contains(&id);
- if let Kind::Import(core) = kind {
+ if let ContextAdapterKind::Import(core) = kind {
if !catch && self.attempt_direct_import(core, instrs)? {
return Ok(());
}
@@ -2358,116 +2824,184 @@ impl<'a> Context<'a> {
// export that we're generating.
let mut builder = binding::Builder::new(self);
builder.log_error(match kind {
- Kind::Export(_) | Kind::Adapter => false,
- Kind::Import(_) => builder.cx.config.debug,
+ ContextAdapterKind::Export(_) | ContextAdapterKind::Adapter => false,
+ ContextAdapterKind::Import(_) => builder.cx.config.debug,
});
builder.catch(catch);
- let mut arg_names = &None;
+ let mut args = &None;
let mut asyncness = false;
+ let mut variadic = false;
+ let mut generate_jsdoc = false;
+ let mut ret_ty_override = &None;
+ let mut ret_desc = &None;
match kind {
- Kind::Export(export) => {
- arg_names = &export.arg_names;
+ ContextAdapterKind::Export(export) => {
+ args = &export.args;
asyncness = export.asyncness;
+ variadic = export.variadic;
+ generate_jsdoc = export.generate_jsdoc;
+ ret_ty_override = &export.fn_ret_ty_override;
+ ret_desc = &export.fn_ret_desc;
match &export.kind {
AuxExportKind::Function(_) => {}
- AuxExportKind::StaticFunction { .. } => {}
AuxExportKind::Constructor(class) => builder.constructor(class),
- AuxExportKind::Getter { consumed, .. }
- | AuxExportKind::Setter { consumed, .. }
- | AuxExportKind::Method { consumed, .. } => builder.method(*consumed),
+ AuxExportKind::Method { receiver, .. } => match receiver {
+ AuxReceiverKind::None => {}
+ AuxReceiverKind::Borrowed => builder.method(false),
+ AuxReceiverKind::Owned => builder.method(true),
+ },
}
}
- Kind::Import(_) => {}
- Kind::Adapter => {}
+ ContextAdapterKind::Import(_) => {}
+ ContextAdapterKind::Adapter => {}
}
+ // an internal debug name to help with error messages
+ let debug_name = match kind {
+ ContextAdapterKind::Import(i) => {
+ let i = builder.cx.module.imports.get(i);
+ format!("import of `{}::{}`", i.module, i.name)
+ }
+ ContextAdapterKind::Export(e) => format!("`{}`", e.debug_name),
+ ContextAdapterKind::Adapter => format!("adapter {}", id.0),
+ };
+
// Process the `binding` and generate a bunch of JS/TypeScript/etc.
let binding::JsFunction {
ts_sig,
ts_arg_tys,
ts_ret_ty,
+ ts_refs,
js_doc,
+ ts_doc,
code,
might_be_optional_field,
catch,
log_error,
} = builder
- .process(&adapter, instrs, arg_names, asyncness)
- .with_context(|| match kind {
- Kind::Export(e) => format!("failed to generate bindings for `{}`", e.debug_name),
- Kind::Import(i) => {
- let i = builder.cx.module.imports.get(i);
- format!(
- "failed to generate bindings for import of `{}::{}`",
- i.module, i.name
- )
- }
- Kind::Adapter => format!("failed to generates bindings for adapter"),
- })?;
+ .process(
+ adapter,
+ instrs,
+ args,
+ asyncness,
+ variadic,
+ generate_jsdoc,
+ &debug_name,
+ ret_ty_override,
+ ret_desc,
+ )
+ .with_context(|| "failed to generates bindings for ".to_string() + &debug_name)?;
+
+ self.typescript_refs.extend(ts_refs);
// Once we've got all the JS then put it in the right location depending
// on what's being exported.
match kind {
- Kind::Export(export) => {
- assert_eq!(catch, false);
- assert_eq!(log_error, false);
+ ContextAdapterKind::Export(export) => {
+ assert!(!catch);
+ assert!(!log_error);
- let ts_sig = match export.generate_typescript {
- true => Some(ts_sig.as_str()),
- false => None,
- };
+ let ts_sig = export.generate_typescript.then_some(ts_sig.as_str());
+
+ // only include `ts_doc` for format if there were arguments or a return var description
+ // this is because if there are no arguments or return var description, `ts_doc`
+ // provides no additional value on top of what `ts_sig` already does
+ let ts_doc_opts = (ret_desc.is_some()
+ || args
+ .as_ref()
+ .is_some_and(|v| v.iter().any(|arg| arg.desc.is_some())))
+ .then_some(ts_doc);
+
+ let js_docs = format_doc_comments(&export.comments, Some(js_doc));
+ let ts_docs = format_doc_comments(&export.comments, ts_doc_opts);
- let docs = format_doc_comments(&export.comments, Some(js_doc));
match &export.kind {
AuxExportKind::Function(name) => {
if let Some(ts_sig) = ts_sig {
- self.typescript.push_str(&docs);
+ self.typescript.push_str(&ts_docs);
self.typescript.push_str("export function ");
- self.typescript.push_str(&name);
+ self.typescript.push_str(name);
self.typescript.push_str(ts_sig);
self.typescript.push_str(";\n");
}
- self.export(&name, &format!("function{}", code), Some(&docs))?;
- self.globals.push_str("\n");
+
+ self.export(
+ name,
+ ExportJs::Function(&format!("function{}", code)),
+ Some(&js_docs),
+ )?;
+ self.globals.push('\n');
}
AuxExportKind::Constructor(class) => {
let exported = require_class(&mut self.exported_classes, class);
+
if exported.has_constructor {
bail!("found duplicate constructor for class `{}`", class);
}
+
exported.has_constructor = true;
- exported.push(&docs, "constructor", "", &code, ts_sig);
+ exported.push("constructor", "", &js_docs, &code, &ts_docs, ts_sig);
}
- AuxExportKind::Getter { class, field, .. } => {
- let ret_ty = match export.generate_typescript {
- true => match &ts_ret_ty {
- Some(s) => Some(s.as_str()),
- _ => None,
- },
- false => None,
- };
+ AuxExportKind::Method {
+ class,
+ name,
+ receiver,
+ kind,
+ } => {
let exported = require_class(&mut self.exported_classes, class);
- exported.push_getter(&docs, field, &code, ret_ty);
- }
- AuxExportKind::Setter { class, field, .. } => {
- let arg_ty = match export.generate_typescript {
- true => Some(ts_arg_tys[0].as_str()),
- false => None,
+
+ let mut prefix = String::new();
+ if receiver.is_static() {
+ prefix += "static ";
+ }
+ let ts = match kind {
+ AuxExportedMethodKind::Method => ts_sig,
+ AuxExportedMethodKind::Getter => {
+ prefix += "get ";
+ // For getters and setters, we generate a separate TypeScript definition.
+ if export.generate_typescript {
+ let location = FieldLocation {
+ name: name.clone(),
+ is_static: receiver.is_static(),
+ };
+ let accessor = FieldAccessor {
+ // This is only set to `None` when generating a constructor.
+ ty: ts_ret_ty.expect("missing return type for getter"),
+ docs: ts_docs.clone(),
+ is_optional: false,
+ };
+
+ exported.push_accessor_ts(location, accessor, false);
+ }
+ // Add the getter to the list of readable fields (used to generate `toJSON`)
+ exported.readable_properties.push(name.clone());
+ // Ignore the raw signature.
+ None
+ }
+ AuxExportedMethodKind::Setter => {
+ prefix += "set ";
+ if export.generate_typescript {
+ let location = FieldLocation {
+ name: name.clone(),
+ is_static: receiver.is_static(),
+ };
+ let accessor = FieldAccessor {
+ ty: ts_arg_tys[0].clone(),
+ docs: ts_docs.clone(),
+ is_optional: might_be_optional_field,
+ };
+
+ exported.push_accessor_ts(location, accessor, true);
+ }
+ None
+ }
};
- let exported = require_class(&mut self.exported_classes, class);
- exported.push_setter(&docs, field, &code, arg_ty, might_be_optional_field);
- }
- AuxExportKind::StaticFunction { class, name } => {
- let exported = require_class(&mut self.exported_classes, class);
- exported.push(&docs, name, "static ", &code, ts_sig);
- }
- AuxExportKind::Method { class, name, .. } => {
- let exported = require_class(&mut self.exported_classes, class);
- exported.push(&docs, name, "", &code, ts_sig);
+
+ exported.push(name, &prefix, &js_docs, &code, &ts_docs, ts);
}
}
}
- Kind::Import(core) => {
+ ContextAdapterKind::Import(core) => {
let code = if catch {
format!(
"function() {{ return handleError(function {}, arguments) }}",
@@ -2484,9 +3018,9 @@ impl<'a> Context<'a> {
self.wasm_import_definitions.insert(core, code);
}
- Kind::Adapter => {
- assert_eq!(catch, false);
- assert_eq!(log_error, false);
+ ContextAdapterKind::Adapter => {
+ assert!(!catch);
+ assert!(!log_error);
self.globals.push_str("function ");
self.globals.push_str(&self.adapter_name(id));
@@ -2494,7 +3028,7 @@ impl<'a> Context<'a> {
self.globals.push_str("\n\n");
}
}
- return Ok(());
+ Ok(())
}
/// Returns whether we should disable the logic, in debug mode, to catch an
@@ -2513,7 +3047,7 @@ impl<'a> Context<'a> {
}
}
- /// Attempts to directly hook up the `id` import in the wasm module with
+ /// Attempts to directly hook up the `id` import in the Wasm module with
/// the `instrs` specified.
///
/// If this succeeds it returns `Ok(true)`, otherwise if it cannot be
@@ -2536,10 +3070,7 @@ impl<'a> Context<'a> {
}
Instruction::CallExport(_)
| Instruction::CallTableElement(_)
- | Instruction::Standard(wit_walrus::Instruction::CallCore(_))
- | Instruction::Standard(wit_walrus::Instruction::CallAdapter(_)) => {
- return Ok(false)
- }
+ | Instruction::CallCore(_) => return Ok(false),
_ => {}
}
}
@@ -2581,30 +3112,30 @@ impl<'a> Context<'a> {
// If there's no field projection happening here and this is a direct
// import from an ES-looking module, then we can actually just hook this
- // up directly in the wasm file itself. Note that this is covered in the
+ // up directly in the Wasm file itself. Note that this is covered in the
// various output formats as well:
//
- // * `bundler` - they think wasm is an ES module anyway
+ // * `bundler` - they think Wasm is an ES module anyway
// * `web` - we're sure to emit more `import` directives during
// `gen_init` and we update the import object accordingly.
- // * `nodejs` - the polyfill we have for requiring a wasm file as a node
+ // * `nodejs` - the polyfill we have for requiring a Wasm file as a node
// module will naturally emit `require` directives for the module
- // listed on each wasm import.
+ // listed on each Wasm import.
// * `no-modules` - imports aren't allowed here anyway from other
// modules and an error is generated.
- if js.fields.len() == 0 {
+ if js.fields.is_empty() {
match &js.name {
JsImportName::Module { module, name } => {
let import = self.module.imports.get_mut(id);
- import.module = module.clone();
- import.name = name.clone();
+ import.module.clone_from(module);
+ import.name.clone_from(name);
return Ok(true);
}
JsImportName::LocalModule { module, name } => {
let module = self.config.local_module_name(module);
let import = self.module.imports.get_mut(id);
import.module = module;
- import.name = name.clone();
+ import.name.clone_from(name);
return Ok(true);
}
JsImportName::InlineJs {
@@ -2617,7 +3148,7 @@ impl<'a> Context<'a> {
.inline_js_module_name(unique_crate_identifier, *snippet_idx_in_crate);
let import = self.module.imports.get_mut(id);
import.module = module;
- import.name = name.clone();
+ import.name.clone_from(name);
return Ok(true);
}
@@ -2629,6 +3160,21 @@ impl<'a> Context<'a> {
}
}
+ if let JsImportName::Global { .. } | JsImportName::VendorPrefixed { .. } = js.name {
+ // We generally cannot import globals directly, because users can
+ // change most globals at runtime.
+ //
+ // An obvious example of this when the object literally changes
+ // (e.g. binding `foo.bar`), but polyfills can also change the
+ // object or fundtion.
+ //
+ // Late binding is another issue. The function might not even be
+ // defined when the Wasm module is instantiated. In such cases,
+ // there is an observable difference between a direct import and a
+ // JS shim calling the function.
+ return Ok(false);
+ }
+
self.expose_not_defined();
let name = self.import_name(js)?;
let js = format!(
@@ -2641,20 +3187,15 @@ impl<'a> Context<'a> {
fn representable_without_js_glue(&self, instrs: &[InstructionData]) -> bool {
use Instruction::*;
- let standard_enabled = self.config.wasm_interface_types;
let mut last_arg = None;
let mut saw_call = false;
for instr in instrs {
match instr.instr {
- // Is an adapter section getting emitted? If so, then every
- // standard operation is natively supported!
- Standard(_) if standard_enabled => {}
-
// Fetching arguments is just that, a fetch, so no need for
// glue. Note though that the arguments must be fetched in order
// for this to actually work,
- Standard(wit_walrus::Instruction::ArgGet(i)) => {
+ ArgGet(i) => {
if saw_call {
return false;
}
@@ -2669,18 +3210,16 @@ impl<'a> Context<'a> {
// needed.
CallAdapter(_) => saw_call = true,
- // Conversions to wasm integers are always supported since
+ // Conversions to Wasm integers are always supported since
// they're coerced into i32/f32/f64 appropriately.
- Standard(wit_walrus::Instruction::IntToWasm { .. }) => {}
+ Int32ToWasm => {}
+ Int64ToWasm => {}
- // Converts from wasm to JS, however, only supports most
- // integers. Converting into a u32 isn't supported because we
+ // Converting into a u32 isn't supported because we
// need to generate glue to change the sign.
- Standard(wit_walrus::Instruction::WasmToInt {
- output: wit_walrus::ValType::U32,
- ..
- }) => return false,
- Standard(wit_walrus::Instruction::WasmToInt { .. }) => {}
+ WasmToInt32 { unsigned_32: false } => {}
+ // A Wasm `i64` is already a signed JS BigInt, so no glue needed.
+ WasmToInt64 { unsigned: false } => {}
// JS spec automatically coerces boolean values to i32 of 0 or 1
// depending on true/false
@@ -2690,13 +3229,13 @@ impl<'a> Context<'a> {
}
}
- return true;
+ true
}
/// Generates a JS snippet appropriate for invoking `import`.
///
/// This is generating code for `binding` where `bindings` has more type
- /// infomation. The `args` array is the list of JS expressions representing
+ /// information. The `args` array is the list of JS expressions representing
/// the arguments to pass to JS. Finally `variadic` indicates whether the
/// last argument is a list to be splatted in a variadic way, and `prelude`
/// is a location to push some more initialization JS if necessary.
@@ -2713,13 +3252,13 @@ impl<'a> Context<'a> {
) -> Result {
let variadic_args = |js_arguments: &[String]| {
Ok(if !variadic {
- format!("{}", js_arguments.join(", "))
+ js_arguments.join(", ")
} else {
let (last_arg, args) = match js_arguments.split_last() {
Some(pair) => pair,
None => bail!("a function with no arguments cannot be variadic"),
};
- if args.len() > 0 {
+ if !args.is_empty() {
format!("{}, ...{}", args.join(", "), last_arg)
} else {
format!("...{}", last_arg)
@@ -2733,7 +3272,7 @@ impl<'a> Context<'a> {
AuxValue::Bare(js) => self.import_name(js)?,
_ => bail!("invalid import set for constructor"),
};
- Ok(format!("new {}({})", js, variadic_args(&args)?))
+ Ok(format!("new {}({})", js, variadic_args(args)?))
}
AdapterJsImportKind::Method => {
let descriptor = |anchor: &str, extra: &str, field: &str, which: &str| {
@@ -2765,20 +3304,25 @@ impl<'a> Context<'a> {
descriptor(&class, "", field, "set")
}
};
- Ok(format!("{}.call({})", js, variadic_args(&args)?))
+ Ok(format!("{}.call({})", js, variadic_args(args)?))
}
AdapterJsImportKind::Normal => {
let js = match val {
AuxValue::Bare(js) => self.import_name(js)?,
_ => bail!("invalid import set for free function"),
};
- Ok(format!("{}({})", js, variadic_args(&args)?))
+ Ok(format!("{}({})", js, variadic_args(args)?))
}
},
AuxImport::ValueWithThis(class, name) => {
let class = self.import_name(class)?;
- Ok(format!("{}.{}({})", class, name, variadic_args(&args)?))
+ Ok(format!(
+ "{}{}({})",
+ class,
+ property_accessor(name),
+ variadic_args(args)?
+ ))
}
AuxImport::Instanceof(js) => {
@@ -2786,21 +3330,46 @@ impl<'a> Context<'a> {
assert!(!variadic);
assert_eq!(args.len(), 1);
let js = self.import_name(js)?;
- Ok(format!("{} instanceof {}", args[0], js))
+ write!(
+ prelude,
+ "\
+ let result;
+ try {{
+ result = {} instanceof {};
+ }} catch (_) {{
+ result = false;
+ }}
+ ",
+ args[0], js,
+ )
+ .unwrap();
+ Ok("result".to_owned())
}
- AuxImport::Static(js) => {
+ AuxImport::Static { js, optional } => {
assert!(kind == AdapterJsImportKind::Normal);
assert!(!variadic);
assert_eq!(args.len(), 0);
- self.import_name(js)
+ self.import_static(js, *optional)
+ }
+
+ AuxImport::String(string) => {
+ assert!(kind == AdapterJsImportKind::Normal);
+ assert!(!variadic);
+ assert_eq!(args.len(), 0);
+
+ let mut escaped = String::with_capacity(string.len());
+ string.chars().for_each(|c| match c {
+ '`' | '\\' | '$' => escaped.extend(['\\', c]),
+ _ => escaped.extend([c]),
+ });
+ Ok(format!("`{escaped}`"))
}
AuxImport::Closure {
dtor,
mutable,
adapter,
- nargs: _,
} => {
assert!(kind == AdapterJsImportKind::Normal);
assert!(!variadic);
@@ -2837,14 +3406,19 @@ impl<'a> Context<'a> {
Some(pair) => pair,
None => bail!("structural method calls must have at least one argument"),
};
- Ok(format!("{}.{}({})", receiver, name, variadic_args(args)?))
+ Ok(format!(
+ "{}{}({})",
+ receiver,
+ property_accessor(name),
+ variadic_args(args)?
+ ))
}
AuxImport::StructuralGetter(field) => {
assert!(kind == AdapterJsImportKind::Normal);
assert!(!variadic);
assert_eq!(args.len(), 1);
- Ok(format!("{}.{}", args[0], field))
+ Ok(format!("{}{}", args[0], property_accessor(field)))
}
AuxImport::StructuralClassGetter(class, field) => {
@@ -2852,14 +3426,19 @@ impl<'a> Context<'a> {
assert!(!variadic);
assert_eq!(args.len(), 0);
let class = self.import_name(class)?;
- Ok(format!("{}.{}", class, field))
+ Ok(format!("{}{}", class, property_accessor(field)))
}
AuxImport::StructuralSetter(field) => {
assert!(kind == AdapterJsImportKind::Normal);
assert!(!variadic);
assert_eq!(args.len(), 2);
- Ok(format!("{}.{} = {}", args[0], field, args[1]))
+ Ok(format!(
+ "{}{} = {}",
+ args[0],
+ property_accessor(field),
+ args[1]
+ ))
}
AuxImport::StructuralClassSetter(class, field) => {
@@ -2867,7 +3446,12 @@ impl<'a> Context<'a> {
assert!(!variadic);
assert_eq!(args.len(), 1);
let class = self.import_name(class)?;
- Ok(format!("{}.{} = {}", class, field, args[0]))
+ Ok(format!(
+ "{}{} = {}",
+ class,
+ property_accessor(field),
+ args[0]
+ ))
}
AuxImport::IndexingGetterOfClass(class) => {
@@ -2928,6 +3512,59 @@ impl<'a> Context<'a> {
assert!(!variadic);
self.invoke_intrinsic(intrinsic, args, prelude)
}
+
+ AuxImport::LinkTo(path, content) => {
+ assert!(kind == AdapterJsImportKind::Normal);
+ assert!(!variadic);
+ assert_eq!(args.len(), 0);
+ if self.config.split_linked_modules {
+ let base = match self.config.mode {
+ OutputMode::Web
+ | OutputMode::Bundler { .. }
+ | OutputMode::Deno
+ | OutputMode::Node { module: true } => "import.meta.url",
+ OutputMode::Node { module: false } => {
+ "require('url').pathToFileURL(__filename)"
+ }
+ OutputMode::NoModules { .. } => {
+ prelude.push_str(
+ "if (script_src === undefined) {
+ throw new Error(
+ \"When `--split-linked-modules` is enabled on the `no-modules` target, \
+ linked modules cannot be used outside of a web page's main thread.\n\
+ \n\
+ To fix this, disable `--split-linked-modules`.\"
+ );
+ }",
+ );
+ "script_src"
+ }
+ };
+ Ok(format!("new URL('{}', {}).toString()", path, base))
+ } else if let Some(content) = content {
+ let mut escaped = String::with_capacity(content.len());
+ content.chars().for_each(|c| match c {
+ '`' | '\\' | '$' => escaped.extend(['\\', c]),
+ _ => escaped.extend([c]),
+ });
+ prelude.push_str(&format!("const val = `{escaped}`;\n"));
+ Ok("typeof URL.createObjectURL === 'undefined' ? \
+ \"data:application/javascript,\" + encodeURIComponent(val) : \
+ URL.createObjectURL(new Blob([val], { type: \"text/javascript\" }))"
+ .to_owned())
+ } else {
+ Err(anyhow!("wasm-bindgen needs to be invoked with `--split-linked-modules`, because \"{}\" cannot be embedded.\n\
+ See https://rustwasm.github.io/wasm-bindgen/reference/cli.html#--split-linked-modules for details.", path))
+ }
+ }
+
+ AuxImport::UnwrapExportedClass(class) => {
+ assert!(kind == AdapterJsImportKind::Normal);
+ assert!(!variadic);
+ assert_eq!(args.len(), 1);
+ self.require_class_unwrap(class);
+ Ok(format!("{}.__unwrap({})", class, args[0]))
+ }
}
}
@@ -2955,6 +3592,11 @@ impl<'a> Context<'a> {
format!("typeof({}) === 'function'", args[0])
}
+ Intrinsic::IsArray => {
+ assert_eq!(args.len(), 1);
+ format!("Array.isArray({})", args[0])
+ }
+
Intrinsic::IsUndefined => {
assert_eq!(args.len(), 1);
format!("{} === undefined", args[0])
@@ -2968,7 +3610,7 @@ impl<'a> Context<'a> {
Intrinsic::IsObject => {
assert_eq!(args.len(), 1);
prelude.push_str(&format!("const val = {};\n", args[0]));
- format!("typeof(val) === 'object' && val !== null")
+ "typeof(val) === 'object' && val !== null".to_string()
}
Intrinsic::IsSymbol => {
@@ -3008,7 +3650,14 @@ impl<'a> Context<'a> {
Intrinsic::TryIntoNumber => {
assert_eq!(args.len(), 1);
- format!("try {{ +{} }} catch(e) {{ e }}", args[0])
+ prelude.push_str("let result;\n");
+ writeln!(
+ prelude,
+ "try {{ result = +{} }} catch (e) {{ result = e }}",
+ args[0]
+ )
+ .unwrap();
+ "result".to_owned()
}
Intrinsic::Neg => {
@@ -3068,7 +3717,22 @@ impl<'a> Context<'a> {
Intrinsic::CheckedDiv => {
assert_eq!(args.len(), 2);
- format!("try {{ {} / {} }} catch (e) {{ if (e instanceof RangeError) {{ e }} else {{ throw e }} }}", args[0], args[1])
+ prelude.push_str("let result;\n");
+ writeln!(
+ prelude,
+ "try {{
+ result = {} / {};
+ }} catch (e) {{
+ if (e instanceof RangeError) {{
+ result = e;
+ }} else {{
+ throw e;
+ }}
+ }}",
+ args[0], args[1]
+ )
+ .unwrap();
+ "result".to_owned()
}
Intrinsic::Mul => {
@@ -3131,11 +3795,21 @@ impl<'a> Context<'a> {
args[0].clone()
}
- Intrinsic::BigIntNew => {
+ Intrinsic::BigIntFromStr => {
assert_eq!(args.len(), 1);
format!("BigInt({})", args[0])
}
+ Intrinsic::BigIntFromI64 | Intrinsic::BigIntFromU64 => {
+ assert_eq!(args.len(), 1);
+ args[0].clone()
+ }
+
+ Intrinsic::BigIntFromI128 | Intrinsic::BigIntFromU128 => {
+ assert_eq!(args.len(), 2);
+ format!("{} << BigInt(64) | {}", args[0], args[1])
+ }
+
Intrinsic::StringNew => {
assert_eq!(args.len(), 1);
args[0].clone()
@@ -3154,19 +3828,25 @@ impl<'a> Context<'a> {
Intrinsic::NumberGet => {
assert_eq!(args.len(), 1);
prelude.push_str(&format!("const obj = {};\n", args[0]));
- format!("typeof(obj) === 'number' ? obj : undefined")
+ "typeof(obj) === 'number' ? obj : undefined".to_string()
}
Intrinsic::StringGet => {
assert_eq!(args.len(), 1);
prelude.push_str(&format!("const obj = {};\n", args[0]));
- format!("typeof(obj) === 'string' ? obj : undefined")
+ "typeof(obj) === 'string' ? obj : undefined".to_string()
}
Intrinsic::BooleanGet => {
assert_eq!(args.len(), 1);
prelude.push_str(&format!("const v = {};\n", args[0]));
- format!("typeof(v) === 'boolean' ? (v ? 1 : 0) : 2")
+ "typeof(v) === 'boolean' ? (v ? 1 : 0) : 2".to_string()
+ }
+
+ Intrinsic::BigIntGetAsI64 => {
+ assert_eq!(args.len(), 1);
+ prelude.push_str(&format!("const v = {};\n", args[0]));
+ "typeof(v) === 'bigint' ? v : undefined".to_string()
}
Intrinsic::Throw => {
@@ -3186,13 +3866,23 @@ impl<'a> Context<'a> {
Intrinsic::Module => {
assert_eq!(args.len(), 0);
- if !self.config.mode.no_modules() && !self.config.mode.web() {
- bail!(
+
+ match self.config.mode {
+ OutputMode::Web | OutputMode::NoModules { .. } => {
+ "__wbg_init.__wbindgen_wasm_module"
+ }
+ OutputMode::Node { .. } => "wasmModule",
+ _ => bail!(
"`wasm_bindgen::module` is currently only supported with \
- `--target no-modules` and `--target web`"
- );
+ `--target no-modules`, `--target web` and `--target nodejs`"
+ ),
}
- format!("init.__wbindgen_wasm_module")
+ .to_string()
+ }
+
+ Intrinsic::Exports => {
+ assert_eq!(args.len(), 0);
+ "wasm".to_string()
}
Intrinsic::Memory => {
@@ -3238,6 +3928,40 @@ impl<'a> Context<'a> {
"JSON.stringify(obj === undefined ? null : obj)".to_string()
}
+ Intrinsic::CopyToTypedArray => {
+ assert_eq!(args.len(), 2);
+ format!(
+ "new Uint8Array({dst}.buffer, {dst}.byteOffset, {dst}.byteLength).set({src})",
+ src = args[0],
+ dst = args[1]
+ )
+ }
+
+ Intrinsic::Uint8ArrayNew
+ | Intrinsic::Uint8ClampedArrayNew
+ | Intrinsic::Uint16ArrayNew
+ | Intrinsic::Uint32ArrayNew
+ | Intrinsic::BigUint64ArrayNew
+ | Intrinsic::Int8ArrayNew
+ | Intrinsic::Int16ArrayNew
+ | Intrinsic::Int32ArrayNew
+ | Intrinsic::BigInt64ArrayNew
+ | Intrinsic::Float32ArrayNew
+ | Intrinsic::Float64ArrayNew => {
+ assert_eq!(args.len(), 1);
+ args[0].clone()
+ }
+
+ Intrinsic::ArrayNew => {
+ assert_eq!(args.len(), 0);
+ "[]".to_string()
+ }
+
+ Intrinsic::ArrayPush => {
+ assert_eq!(args.len(), 2);
+ format!("{}.push({})", args[0], args[1])
+ }
+
Intrinsic::ExternrefHeapLiveCount => {
assert_eq!(args.len(), 0);
self.expose_global_heap();
@@ -3287,11 +4011,11 @@ impl<'a> Context<'a> {
}
fn generate_enum(&mut self, enum_: &AuxEnum) -> Result<(), Error> {
- let docs = format_doc_comments(&enum_.comments, None);
let mut variants = String::new();
if enum_.generate_typescript {
- self.typescript.push_str(&docs);
+ self.typescript
+ .push_str(&format_doc_comments(&enum_.comments, None));
self.typescript
.push_str(&format!("export enum {} {{", enum_.name));
}
@@ -3299,38 +4023,95 @@ impl<'a> Context<'a> {
let variant_docs = if comments.is_empty() {
String::new()
} else {
- format_doc_comments(&comments, None)
+ format_doc_comments(comments, None)
};
- if !variant_docs.is_empty() {
- variants.push_str("\n");
- variants.push_str(&variant_docs);
- }
- variants.push_str(&format!("{}:{},", name, value));
- variants.push_str(&format!("\"{}\":\"{}\",", value, name));
+ variants.push_str(&variant_docs);
+ variants.push_str(&format!("{}: {}, ", name, value));
+ variants.push_str(&format!("\"{}\": \"{}\",\n", value, name));
if enum_.generate_typescript {
- self.typescript.push_str("\n");
+ self.typescript.push('\n');
if !variant_docs.is_empty() {
- self.typescript.push_str(&variant_docs);
+ for line in variant_docs.lines() {
+ self.typescript.push_str(" ");
+ self.typescript.push_str(line);
+ self.typescript.push('\n');
+ }
}
- self.typescript.push_str(&format!(" {},", name));
+ self.typescript.push_str(&format!(" {name} = {value},"));
}
}
if enum_.generate_typescript {
self.typescript.push_str("\n}\n");
}
+
+ // add an `@enum {1 | 2 | 3}` to ensure that enums type-check even without .d.ts
+ let mut at_enum = "@enum {".to_string();
+ for (i, (_, value, _)) in enum_.variants.iter().enumerate() {
+ if i != 0 {
+ at_enum.push_str(" | ");
+ }
+ at_enum.push_str(&value.to_string());
+ }
+ at_enum.push('}');
+ let docs = format_doc_comments(&enum_.comments, Some(at_enum));
+
self.export(
&enum_.name,
- &format!("Object.freeze({{ {} }})", variants),
+ ExportJs::Expression(&format!("Object.freeze({{\n{}}})", variants)),
Some(&docs),
)?;
Ok(())
}
+ fn generate_string_enum(&mut self, string_enum: &AuxStringEnum) -> Result<(), Error> {
+ let variants: Vec<_> = string_enum
+ .variant_values
+ .iter()
+ .map(|v| format!("\"{v}\""))
+ .collect();
+
+ if string_enum.generate_typescript
+ && self
+ .typescript_refs
+ .contains(&TsReference::StringEnum(string_enum.name.clone()))
+ {
+ let docs = format_doc_comments(&string_enum.comments, None);
+ let type_expr = if variants.is_empty() {
+ "never".to_string()
+ } else {
+ variants.join(" | ")
+ };
+
+ self.typescript.push_str(&docs);
+ self.typescript.push_str("type ");
+ self.typescript.push_str(&string_enum.name);
+ self.typescript.push_str(" = ");
+ self.typescript.push_str(&type_expr);
+ self.typescript.push_str(";\n");
+ }
+
+ if self.used_string_enums.contains(&string_enum.name) {
+ // only generate the internal string enum array if it's actually used
+ self.global(&format!(
+ "const __wbindgen_enum_{name} = [{values}];\n",
+ name = string_enum.name,
+ values = variants.join(", ")
+ ));
+ }
+
+ Ok(())
+ }
+
+ fn expose_string_enum(&mut self, string_enum_name: &str) {
+ self.used_string_enums.insert(string_enum_name.to_string());
+ }
+
fn generate_struct(&mut self, struct_: &AuxStruct) -> Result<(), Error> {
let class = require_class(&mut self.exported_classes, &struct_.name);
class.comments = format_doc_comments(&struct_.comments, None);
class.is_inspectable = struct_.is_inspectable;
+ class.generate_typescript = struct_.generate_typescript;
Ok(())
}
@@ -3353,9 +4134,9 @@ impl<'a> Context<'a> {
path.display()
),
};
- let mut iter = object.iter();
+ let iter = object.iter();
let mut value = None;
- while let Some((key, v)) = iter.next() {
+ for (key, v) in iter {
if key == "dependencies" {
value = Some(v);
break;
@@ -3447,7 +4228,7 @@ impl<'a> Context<'a> {
// Test for built-in
const builtInMatches = /\\[object ([^\\]]+)\\]/.exec(toString.call(val));
let className;
- if (builtInMatches.length > 1) {
+ if (builtInMatches && builtInMatches.length > 1) {
className = builtInMatches[1];
} else {
// Failed to match the standard '[object ClassName]'
@@ -3504,8 +4285,18 @@ impl<'a> Context<'a> {
"memory".to_owned()
}
walrus::ExportItem::Function(f) => match &self.module.funcs.get(f).name {
- Some(s) => to_js_identifier(s),
- None => default_name,
+ Some(s) => {
+ let mut name = to_js_identifier(s);
+
+ // Account for duplicate export names.
+ // See https://github.com/rustwasm/wasm-bindgen/issues/4371.
+ if self.module.exports.get_func(&name).is_ok() {
+ name.push_str(&self.next_export_idx.to_string());
+ }
+
+ name
+ }
+ _ => default_name,
},
_ => default_name,
};
@@ -3549,13 +4340,13 @@ impl<'a> Context<'a> {
if self.stack_pointer_shim_injected {
return Ok(());
}
- let stack_pointer = match self.aux.shadow_stack_pointer {
+ let stack_pointer = match self.aux.stack_pointer {
Some(s) => s,
// In theory this shouldn't happen since malloc is included in
- // most wasm binaries (and may be gc'd out) and that almost
+ // most Wasm binaries (and may be gc'd out) and that almost
// always pulls in a stack pointer. We can try to synthesize
// something here later if necessary.
- None => bail!("failed to find shadow stack pointer"),
+ None => bail!("failed to find stack pointer"),
};
use walrus::ir::*;
@@ -3587,48 +4378,169 @@ impl<'a> Context<'a> {
}
}
+/// A categorization of adapters for the purpose of code generation.
+///
+/// This is different from [`AdapterKind`] and is only used internally in the
+/// code generation process.
+enum ContextAdapterKind<'a> {
+ /// An exported function, method, constrctor, or getter/setter.
+ Export(&'a AuxExport),
+ /// An imported function or intrinsic.
+ Import(walrus::ImportId),
+ Adapter,
+}
+impl<'a> ContextAdapterKind<'a> {
+ fn get(id: AdapterId, aux: &'a WasmBindgenAux, wit: &'a NonstandardWitSection) -> Self {
+ match aux.export_map.get(&id) {
+ Some(export) => ContextAdapterKind::Export(export),
+ None => {
+ let core = wit.implements.iter().find(|pair| pair.2 == id);
+ match core {
+ Some((core, _, _)) => ContextAdapterKind::Import(*core),
+ None => ContextAdapterKind::Adapter,
+ }
+ }
+ }
+ }
+}
+
+/// Iterate over the adapters in a deterministic order.
+fn iter_adapeter<'a>(
+ aux: &'a WasmBindgenAux,
+ wit: &'a NonstandardWitSection,
+ module: &Module,
+) -> Vec<(AdapterId, &'a Adapter, ContextAdapterKind<'a>)> {
+ let mut adapters: Vec<_> = wit
+ .adapters
+ .iter()
+ .map(|(id, adapter)| {
+ // we need the kind of the adapter to properly sort them
+ let kind = ContextAdapterKind::get(*id, aux, wit);
+ (*id, adapter, kind)
+ })
+ .collect();
+
+ // Since `wit.adapters` is a BTreeMap, the adapters are already sorted by
+ // their ID. This is good enough for exports and adapters, but imports need
+ // to be sorted by their name.
+ //
+ // Note: we do *NOT* want to sort exports by name. By default, exports are
+ // the order in which they were defined in the Rust code. Sorting them by
+ // name would break that order and take away control from the user.
+
+ adapters.sort_by(|(_, _, a), (_, _, b)| {
+ fn get_kind_order(kind: &ContextAdapterKind) -> u8 {
+ match kind {
+ ContextAdapterKind::Import(_) => 0,
+ ContextAdapterKind::Export(_) => 1,
+ ContextAdapterKind::Adapter => 2,
+ }
+ }
+
+ match (a, b) {
+ (ContextAdapterKind::Import(a), ContextAdapterKind::Import(b)) => {
+ let a = module.imports.get(*a);
+ let b = module.imports.get(*b);
+ a.name.cmp(&b.name)
+ }
+ _ => get_kind_order(a).cmp(&get_kind_order(b)),
+ }
+ });
+
+ adapters
+}
+
+/// Iterate over the imports in a deterministic order.
+fn iter_by_import<'a, T>(
+ map: &'a HashMap,
+ module: &Module,
+) -> Vec<(&'a ImportId, &'a T)> {
+ let mut items: Vec<_> = map.iter().collect();
+
+ // Sort by import name.
+ //
+ // Imports have a name and a module, and it's important that we *ignore*
+ // the module. The module of an import is set to its final value *during*
+ // code generation, so using it here would cause the imports to be sorted
+ // differently depending on which part of the code generation process we're
+ // in.
+ items.sort_by(|&(a, _), &(b, _)| {
+ let a = module.imports.get(*a);
+ let b = module.imports.get(*b);
+
+ a.name.cmp(&b.name)
+ });
+
+ items
+}
+
fn check_duplicated_getter_and_setter_names(
exports: &[(&AdapterId, &AuxExport)],
) -> Result<(), Error> {
- let verify_exports =
- |first_class, first_field, second_class, second_field| -> Result<(), Error> {
- let both_are_in_the_same_class = first_class == second_class;
- let both_are_referencing_the_same_field = first_field == second_field;
- if both_are_in_the_same_class && both_are_referencing_the_same_field {
- bail!(format!(
- "There can be only one getter/setter definition for `{}` in `{}`",
- first_field, first_class
- ));
- }
- Ok(())
- };
+ fn verify_exports(
+ first_class: &str,
+ first_field: &str,
+ first_receiver: &AuxReceiverKind,
+ second_class: &str,
+ second_field: &str,
+ second_receiver: &AuxReceiverKind,
+ ) -> Result<(), Error> {
+ let both_are_in_the_same_class = first_class == second_class;
+ let both_are_referencing_the_same_field = first_field == second_field
+ && first_receiver.is_static() == second_receiver.is_static();
+ if both_are_in_the_same_class && both_are_referencing_the_same_field {
+ bail!(format!(
+ "There can be only one getter/setter definition for `{}` in `{}`",
+ first_field, first_class
+ ));
+ }
+ Ok(())
+ }
for (idx, (_, first_export)) in exports.iter().enumerate() {
for (_, second_export) in exports.iter().skip(idx + 1) {
match (&first_export.kind, &second_export.kind) {
(
- AuxExportKind::Getter {
+ AuxExportKind::Method {
class: first_class,
- field: first_field,
- consumed: _,
+ name: first_name,
+ kind: AuxExportedMethodKind::Getter,
+ receiver: first_receiver,
},
- AuxExportKind::Getter {
+ AuxExportKind::Method {
class: second_class,
- field: second_field,
- consumed: _,
+ name: second_name,
+ kind: AuxExportedMethodKind::Getter,
+ receiver: second_receiver,
},
- ) => verify_exports(first_class, first_field, second_class, second_field)?,
+ ) => verify_exports(
+ first_class,
+ first_name,
+ first_receiver,
+ second_class,
+ second_name,
+ second_receiver,
+ )?,
(
- AuxExportKind::Setter {
+ AuxExportKind::Method {
class: first_class,
- field: first_field,
- consumed: _,
+ name: first_name,
+ kind: AuxExportedMethodKind::Setter,
+ receiver: first_receiver,
},
- AuxExportKind::Setter {
+ AuxExportKind::Method {
class: second_class,
- field: second_field,
- consumed: _,
+ name: second_name,
+ kind: AuxExportedMethodKind::Setter,
+ receiver: second_receiver,
},
- ) => verify_exports(first_class, first_field, second_class, second_field)?,
+ ) => verify_exports(
+ first_class,
+ first_name,
+ first_receiver,
+ second_class,
+ second_name,
+ second_receiver,
+ )?,
_ => {}
}
}
@@ -3637,13 +4549,29 @@ fn check_duplicated_getter_and_setter_names(
}
fn format_doc_comments(comments: &str, js_doc_comments: Option) -> String {
- let body: String = comments.lines().map(|c| format!("*{}\n", c)).collect();
+ let body: String = comments.lines().fold(String::new(), |mut output, c| {
+ output.push_str(" *");
+ if !c.is_empty() && !c.starts_with(' ') {
+ output.push(' ');
+ }
+ output.push_str(c);
+ output.push('\n');
+ output
+ });
let doc = if let Some(docs) = js_doc_comments {
- docs.lines().map(|l| format!("* {}\n", l)).collect()
+ docs.lines().fold(String::new(), |mut output: String, l| {
+ let _ = writeln!(output, " * {}", l);
+ output
+ })
} else {
String::new()
};
- format!("/**\n{}{}*/\n", body, doc)
+ if body.is_empty() && doc.is_empty() {
+ // don't emit empty doc comments
+ String::new()
+ } else {
+ format!("/**\n{}{} */\n", body, doc)
+ }
}
fn require_class<'a>(
@@ -3654,25 +4582,46 @@ fn require_class<'a>(
.as_mut()
.expect("classes already written")
.entry(name.to_string())
- .or_insert_with(ExportedClass::default)
+ .or_default()
+}
+
+/// Returns a string to tack on to the end of an expression to access a
+/// property named `name` of the object that expression resolves to.
+///
+/// In most cases, this is `.`, generating accesses like `foo.bar`.
+/// However, if `name` is not a valid JavaScript identifier, it becomes
+/// `[""]` instead, creating accesses like `foo["kebab-case"]`.
+fn property_accessor(name: &str) -> String {
+ if is_valid_ident(name) {
+ format!(".{name}")
+ } else {
+ format!("[\"{}\"]", name.escape_default())
+ }
}
impl ExportedClass {
fn push(
&mut self,
- docs: &str,
function_name: &str,
function_prefix: &str,
+ js_docs: &str,
js: &str,
+ ts_docs: &str,
ts: Option<&str>,
) {
- self.contents.push_str(docs);
+ self.contents.push_str(js_docs);
self.contents.push_str(function_prefix);
self.contents.push_str(function_name);
self.contents.push_str(js);
- self.contents.push_str("\n");
+ self.contents.push('\n');
if let Some(ts) = ts {
- self.typescript.push_str(docs);
+ if !ts_docs.is_empty() {
+ for line in ts_docs.lines() {
+ self.typescript.push_str(" ");
+ self.typescript.push_str(line);
+ self.typescript.push('\n');
+ }
+ }
self.typescript.push_str(" ");
self.typescript.push_str(function_prefix);
self.typescript.push_str(function_name);
@@ -3681,65 +4630,34 @@ impl ExportedClass {
}
}
- /// Used for adding a getter to a class, mainly to ensure that TypeScript
- /// generation is handled specially.
- fn push_getter(&mut self, docs: &str, field: &str, js: &str, ret_ty: Option<&str>) {
- self.push_accessor(docs, field, js, "get ");
- if let Some(ret_ty) = ret_ty {
- self.push_accessor_ts(docs, field, ret_ty, false);
- }
- self.readable_properties.push(field.to_string());
- }
-
- /// Used for adding a setter to a class, mainly to ensure that TypeScript
- /// generation is handled specially.
- fn push_setter(
- &mut self,
- docs: &str,
- field: &str,
- js: &str,
- ret_ty: Option<&str>,
- might_be_optional_field: bool,
- ) {
- self.push_accessor(docs, field, js, "set ");
- if let Some(ret_ty) = ret_ty {
- let is_optional = self.push_accessor_ts(docs, field, ret_ty, true);
- *is_optional = might_be_optional_field;
- }
- }
-
fn push_accessor_ts(
&mut self,
- docs: &str,
- field: &str,
- ret_ty: &str,
+ location: FieldLocation,
+ accessor: FieldAccessor,
is_setter: bool,
- ) -> &mut bool {
- let (ty, accessor_docs, has_setter, is_optional) = self
+ ) {
+ let size = self.typescript_fields.len();
+ let field = self
.typescript_fields
- .entry(field.to_string())
- .or_insert_with(Default::default);
-
- *ty = ret_ty.to_string();
- // Deterministic output: always use the getter's docs if available
- if !docs.is_empty() && (accessor_docs.is_empty() || !is_setter) {
- *accessor_docs = docs.to_owned();
+ .entry(location)
+ .or_insert_with_key(|location| FieldInfo {
+ name: location.name.to_string(),
+ is_static: location.is_static,
+ order: size,
+ getter: None,
+ setter: None,
+ });
+
+ if is_setter {
+ field.setter = Some(accessor);
+ } else {
+ field.getter = Some(accessor);
}
- *has_setter |= is_setter;
- is_optional
- }
-
- fn push_accessor(&mut self, docs: &str, field: &str, js: &str, prefix: &str) {
- self.contents.push_str(docs);
- self.contents.push_str(prefix);
- self.contents.push_str(field);
- self.contents.push_str(js);
- self.contents.push_str("\n");
}
}
struct MemView {
- name: &'static str,
+ name: Cow<'static, str>,
num: usize,
}
diff --git a/crates/cli-support/src/lib.rs b/crates/cli-support/src/lib.rs
index d767be55d7e..a07f48306a1 100755
--- a/crates/cli-support/src/lib.rs
+++ b/crates/cli-support/src/lib.rs
@@ -18,7 +18,6 @@ mod externref;
mod intrinsic;
mod js;
mod multivalue;
-mod throw2unreachable;
pub mod wasm2es6js;
mod wit;
@@ -30,21 +29,17 @@ pub struct Bindgen {
typescript: bool,
omit_imports: bool,
demangle: bool,
+ keep_lld_exports: bool,
keep_debug: bool,
remove_name_section: bool,
remove_producers_section: bool,
omit_default_module_path: bool,
emit_start: bool,
- // Experimental support for weakrefs, an upcoming ECMAScript feature.
- // Currently only enable-able through an env var.
- weak_refs: bool,
- // Support for the wasm threads proposal, transforms the wasm module to be
- // "ready to be instantiated on any thread"
- threads: wasm_bindgen_threads_xform::Config,
externref: bool,
multi_value: bool,
- wasm_interface_types: bool,
encode_into: EncodeInto,
+ split_linked_modules: bool,
+ symbol_dispose: bool,
}
pub struct Output {
@@ -53,12 +48,7 @@ pub struct Output {
generated: Generated,
}
-enum Generated {
- InterfaceTypes,
- Js(JsGenerated),
-}
-
-struct JsGenerated {
+struct Generated {
mode: OutputMode,
js: String,
ts: String,
@@ -74,13 +64,14 @@ enum OutputMode {
Bundler { browser_only: bool },
Web,
NoModules { global: String },
- Node { experimental_modules: bool },
+ Node { module: bool },
Deno,
}
enum Input {
Path(PathBuf),
Module(Module, String),
+ Bytes(Vec, String),
None,
}
@@ -94,8 +85,8 @@ impl Bindgen {
pub fn new() -> Bindgen {
let externref =
env::var("WASM_BINDGEN_ANYREF").is_ok() || env::var("WASM_BINDGEN_EXTERNREF").is_ok();
- let wasm_interface_types = env::var("WASM_INTERFACE_TYPES").is_ok();
let multi_value = env::var("WASM_BINDGEN_MULTI_VALUE").is_ok();
+ let symbol_dispose = env::var("WASM_BINDGEN_EXPERIMENTAL_SYMBOL_DISPOSE").is_ok();
Bindgen {
input: Input::None,
out_name: None,
@@ -106,17 +97,17 @@ impl Bindgen {
typescript: false,
omit_imports: false,
demangle: true,
+ keep_lld_exports: false,
keep_debug: false,
remove_name_section: false,
remove_producers_section: false,
emit_start: true,
- weak_refs: env::var("WASM_BINDGEN_WEAKREF").is_ok(),
- threads: threads_config(),
- externref: externref || wasm_interface_types,
- multi_value: multi_value || wasm_interface_types,
- wasm_interface_types,
+ externref,
+ multi_value,
encode_into: EncodeInto::Test,
omit_default_module_path: true,
+ split_linked_modules: false,
+ symbol_dispose,
}
}
@@ -130,11 +121,7 @@ impl Bindgen {
self
}
- pub fn weak_refs(&mut self, enable: bool) -> &mut Bindgen {
- self.weak_refs = enable;
- self
- }
-
+ #[deprecated = "automatically detected via `-Ctarget-feature=+reference-types`"]
pub fn reference_types(&mut self, enable: bool) -> &mut Bindgen {
self.externref = enable;
self
@@ -144,7 +131,14 @@ impl Bindgen {
pub fn input_module(&mut self, name: &str, module: Module) -> &mut Bindgen {
let name = name.to_string();
self.input = Input::Module(module, name);
- return self;
+ self
+ }
+
+ /// Specify the input as the provided Wasm bytes.
+ pub fn input_bytes(&mut self, name: &str, bytes: Vec) -> &mut Bindgen {
+ let name = name.to_string();
+ self.input = Input::Bytes(bytes, name);
+ self
}
fn switch_mode(&mut self, mode: OutputMode, flag: &str) -> Result<(), Error> {
@@ -160,23 +154,16 @@ impl Bindgen {
pub fn nodejs(&mut self, node: bool) -> Result<&mut Bindgen, Error> {
if node {
- self.switch_mode(
- OutputMode::Node {
- experimental_modules: false,
- },
- "--target nodejs",
- )?;
+ self.switch_mode(OutputMode::Node { module: false }, "--target nodejs")?;
}
Ok(self)
}
- pub fn nodejs_experimental_modules(&mut self, node: bool) -> Result<&mut Bindgen, Error> {
+ pub fn nodejs_module(&mut self, node: bool) -> Result<&mut Bindgen, Error> {
if node {
self.switch_mode(
- OutputMode::Node {
- experimental_modules: true,
- },
- "--nodejs-experimental-modules",
+ OutputMode::Node { module: true },
+ "--target experimental-nodejs-module",
)?;
}
Ok(self)
@@ -259,6 +246,11 @@ impl Bindgen {
self
}
+ pub fn keep_lld_exports(&mut self, keep_lld_exports: bool) -> &mut Bindgen {
+ self.keep_lld_exports = keep_lld_exports;
+ self
+ }
+
pub fn keep_debug(&mut self, keep_debug: bool) -> &mut Bindgen {
self.keep_debug = keep_debug;
self
@@ -289,6 +281,11 @@ impl Bindgen {
self
}
+ pub fn split_linked_modules(&mut self, split_linked_modules: bool) -> &mut Bindgen {
+ self.split_linked_modules = split_linked_modules;
+ self
+ }
+
pub fn generate>(&mut self, path: P) -> Result<(), Error> {
self.generate_output()?.emit(path.as_ref())
}
@@ -296,7 +293,7 @@ impl Bindgen {
pub fn stem(&self) -> Result<&str, Error> {
Ok(match &self.input {
Input::None => bail!("must have an input by now"),
- Input::Module(_, name) => name,
+ Input::Module(_, name) | Input::Bytes(_, name) => name,
Input::Path(path) => match &self.out_name {
Some(name) => name,
None => path.file_stem().unwrap().to_str().unwrap(),
@@ -312,30 +309,36 @@ impl Bindgen {
mem::replace(m, blank_module)
}
Input::Path(ref path) => {
- let wasm = wit_text::parse_file(&path)
- .with_context(|| format!("failed to read `{}`", path.display()))?;
- wit_validator::validate(&wasm)
- .with_context(|| format!("failed to validate `{}`", path.display()))?;
- let module = walrus::ModuleConfig::new()
- // Skip validation of the module as LLVM's output is
- // generally already well-formed and so we won't gain much
- // from re-validating. Additionally LLVM's current output
- // for threads includes atomic instructions but doesn't
- // include shared memory, so it fails that part of
- // validation!
- .strict_validate(false)
- .generate_dwarf(self.keep_debug)
- .generate_name_section(!self.remove_name_section)
- .generate_producers_section(!self.remove_producers_section)
- .on_parse(wit_walrus::on_parse)
- .parse(&wasm)
- .context("failed to parse input file as wasm")?;
- module
+ let bytes = std::fs::read(path)
+ .with_context(|| format!("failed reading '{}'", path.display()))?;
+ self.module_from_bytes(&bytes).with_context(|| {
+ format!("failed getting Wasm module for '{}'", path.display())
+ })?
}
+ Input::Bytes(ref bytes, _) => self
+ .module_from_bytes(bytes)
+ .context("failed getting Wasm module")?,
};
- self.threads
- .run(&mut module)
+ // Enable reference type transformations if the module is already using it.
+ if let Ok(true) = wasm_bindgen_wasm_conventions::target_feature(&module, "reference-types")
+ {
+ self.externref = true;
+ }
+
+ // Enable multivalue transformations if the module is already using it.
+ if let Ok(true) = wasm_bindgen_wasm_conventions::target_feature(&module, "multivalue") {
+ self.multi_value = true;
+ }
+
+ // Check that no exported symbol is called "default" if we target web.
+ if matches!(self.mode, OutputMode::Web)
+ && module.exports.iter().any(|export| export.name == "default")
+ {
+ bail!("exported symbol \"default\" not allowed for --target web")
+ }
+
+ let thread_count = wasm_bindgen_threads_xform::run(&mut module)
.with_context(|| "failed to prepare module for threading")?;
// If requested, turn all mangled symbols into prettier unmangled
@@ -343,31 +346,38 @@ impl Bindgen {
if self.demangle {
demangle(&mut module);
}
- unexported_unused_lld_things(&mut module);
+ if !self.keep_lld_exports {
+ unexported_unused_lld_things(&mut module);
+ }
// We're making quite a few changes, list ourselves as a producer.
module
.producers
.add_processed_by("wasm-bindgen", &wasm_bindgen_shared::version());
+ // Parse and remove our custom section before executing descriptors.
+ // That includes checking that the binary has the same schema version
+ // as this version of the CLI, which is why we do it first - to make
+ // sure that this binary was produced by a compatible version of the
+ // wasm-bindgen macro before attempting to interpret our unstable
+ // descriptor format. That way, we give a more helpful version mismatch
+ // error instead of an unhelpful panic if an incompatible descriptor is
+ // found.
+ let mut storage = Vec::new();
+ let programs = wit::extract_programs(&mut module, &mut storage)?;
+
// Learn about the type signatures of all wasm-bindgen imports and
// exports by executing `__wbindgen_describe_*` functions. This'll
// effectively move all the descriptor functions to their own custom
// sections.
descriptors::execute(&mut module)?;
- // Process and remove our raw custom sections emitted by the
- // #[wasm_bindgen] macro and the compiler. In their stead insert a
- // forward-compatible wasm interface types section as well as an
+ // Process the custom section we extracted earlier. In its stead insert
+ // a forward-compatible Wasm interface types section as well as an
// auxiliary section for all sorts of miscellaneous information and
// features #[wasm_bindgen] supports that aren't covered by wasm
// interface types.
- wit::process(
- &mut module,
- self.externref,
- self.wasm_interface_types,
- self.emit_start,
- )?;
+ wit::process(self, &mut module, programs, thread_count)?;
// Now that we've got type information from the webidl processing pass,
// touch up the output of rustc to insert externref shims where necessary.
@@ -397,66 +407,41 @@ impl Bindgen {
externref::force_contiguous_elements(&mut module)?;
}
- // If wasm interface types are enabled then the `__wbindgen_throw`
- // intrinsic isn't available but it may be used by our runtime, so
- // change all calls to this function to calls to `unreachable` instead.
- // See more documentation in the pass documentation itself.
- if self.wasm_interface_types {
- throw2unreachable::run(&mut module);
- }
-
// Using all of our metadata convert our module to a multi-value using
// module if applicable.
if self.multi_value {
- if !self.wasm_interface_types {
- anyhow::bail!(
- "Wasm multi-value is currently only available when \
- Wasm interface types is also enabled"
- );
- }
multivalue::run(&mut module)
.context("failed to transform return pointers into multi-value Wasm")?;
}
- // We've done a whole bunch of transformations to the wasm module, many
+ // We've done a whole bunch of transformations to the Wasm module, many
// of which leave "garbage" lying around, so let's prune out all our
// unnecessary things here.
gc_module_and_adapters(&mut module);
let stem = self.stem()?;
- // We're ready for the final emission passes now. If we're in wasm
- // interface types mode then we execute the various passes there and
- // generate a valid interface types section into the wasm module.
- //
- // Otherwise we execute the JS generation passes to actually emit
- // JS/TypeScript/etc. The output here is unused in wasm interfac
- let generated = if self.wasm_interface_types {
- wit::section::add(&mut module)
- .context("failed to generate a standard interface types section")?;
- Generated::InterfaceTypes
- } else {
- let aux = module
- .customs
- .delete_typed::()
- .expect("aux section should be present");
- let adapters = module
- .customs
- .delete_typed::()
- .unwrap();
- let mut cx = js::Context::new(&mut module, self, &adapters, &aux)?;
- cx.generate()?;
- let (js, ts, start) = cx.finalize(stem)?;
- Generated::Js(JsGenerated {
- snippets: aux.snippets.clone(),
- local_modules: aux.local_modules.clone(),
- mode: self.mode.clone(),
- typescript: self.typescript,
- npm_dependencies: cx.npm_dependencies.clone(),
- js,
- ts,
- start,
- })
+ // Now we execute the JS generation passes to actually emit JS/TypeScript/etc.
+ let aux = module
+ .customs
+ .delete_typed::()
+ .expect("aux section should be present");
+ let adapters = module
+ .customs
+ .delete_typed::()
+ .unwrap();
+ let mut cx = js::Context::new(&mut module, self, &adapters, &aux)?;
+ cx.generate()?;
+ let (js, ts, start) = cx.finalize(stem)?;
+ let generated = Generated {
+ snippets: aux.snippets.clone(),
+ local_modules: aux.local_modules.clone(),
+ mode: self.mode.clone(),
+ typescript: self.typescript,
+ npm_dependencies: cx.npm_dependencies.clone(),
+ js,
+ ts,
+ start,
};
Ok(Output {
@@ -466,6 +451,22 @@ impl Bindgen {
})
}
+ fn module_from_bytes(&self, bytes: &[u8]) -> Result {
+ walrus::ModuleConfig::new()
+ // Skip validation of the module as LLVM's output is
+ // generally already well-formed and so we won't gain much
+ // from re-validating. Additionally LLVM's current output
+ // for threads includes atomic instructions but doesn't
+ // include shared memory, so it fails that part of
+ // validation!
+ .strict_validate(false)
+ .generate_dwarf(self.keep_debug)
+ .generate_name_section(!self.remove_name_section)
+ .generate_producers_section(!self.remove_producers_section)
+ .parse(bytes)
+ .context("failed to parse input as wasm")
+ }
+
fn local_module_name(&self, module: &str) -> String {
format!("./snippets/{}", module)
}
@@ -486,11 +487,28 @@ fn reset_indentation(s: &str) -> String {
let mut indent: u32 = 0;
let mut dst = String::new();
+ fn is_doc_comment(line: &str) -> bool {
+ line.starts_with("*")
+ }
+
for line in s.lines() {
let line = line.trim();
- if line.starts_with('}') || (line.ends_with('}') && !line.starts_with('*')) {
+
+ // handle doc comments separately
+ if is_doc_comment(line) {
+ for _ in 0..indent {
+ dst.push_str(" ");
+ }
+ dst.push(' ');
+ dst.push_str(line);
+ dst.push('\n');
+ continue;
+ }
+
+ if line.starts_with('}') {
indent = indent.saturating_sub(1);
}
+
let extra = if line.starts_with(':') || line.starts_with('?') {
1
} else {
@@ -502,27 +520,13 @@ fn reset_indentation(s: &str) -> String {
}
dst.push_str(line);
}
- dst.push_str("\n");
- // Ignore { inside of comments and if it's an exported enum
- if line.ends_with('{') && !line.starts_with('*') && !line.ends_with("Object.freeze({") {
+ dst.push('\n');
+
+ if line.ends_with('{') {
indent += 1;
}
}
- return dst;
-}
-
-// Eventually these will all be CLI options, but while they're unstable features
-// they're left as environment variables. We don't guarantee anything about
-// backwards-compatibility with these options.
-fn threads_config() -> wasm_bindgen_threads_xform::Config {
- let mut cfg = wasm_bindgen_threads_xform::Config::new();
- if let Ok(s) = env::var("WASM_BINDGEN_THREADS_MAX_MEMORY") {
- cfg.maximum_memory(s.parse().unwrap());
- }
- if let Ok(s) = env::var("WASM_BINDGEN_THREADS_STACK_SIZE") {
- cfg.thread_stack_size(s.parse().unwrap());
- }
- cfg
+ dst
}
fn demangle(module: &mut Module) {
@@ -539,55 +543,28 @@ fn demangle(module: &mut Module) {
impl OutputMode {
fn uses_es_modules(&self) -> bool {
- match self {
+ matches!(
+ self,
OutputMode::Bundler { .. }
- | OutputMode::Web
- | OutputMode::Node {
- experimental_modules: true,
- }
- | OutputMode::Deno => true,
- _ => false,
- }
- }
-
- fn nodejs_experimental_modules(&self) -> bool {
- match self {
- OutputMode::Node {
- experimental_modules,
- } => *experimental_modules,
- _ => false,
- }
+ | OutputMode::Web
+ | OutputMode::Node { module: true }
+ | OutputMode::Deno
+ )
}
fn nodejs(&self) -> bool {
- match self {
- OutputMode::Node { .. } => true,
- _ => false,
- }
+ matches!(self, OutputMode::Node { .. })
}
fn no_modules(&self) -> bool {
- match self {
- OutputMode::NoModules { .. } => true,
- _ => false,
- }
- }
-
- fn web(&self) -> bool {
- match self {
- OutputMode::Web => true,
- _ => false,
- }
+ matches!(self, OutputMode::NoModules { .. })
}
fn esm_integration(&self) -> bool {
- match self {
- OutputMode::Bundler { .. }
- | OutputMode::Node {
- experimental_modules: true,
- } => true,
- _ => false,
- }
+ matches!(
+ self,
+ OutputMode::Bundler { .. } | OutputMode::Node { module: true }
+ )
}
}
@@ -611,12 +588,33 @@ fn unexported_unused_lld_things(module: &mut Module) {
impl Output {
pub fn js(&self) -> &str {
- match &self.generated {
- Generated::InterfaceTypes => panic!("no js with interface types output"),
- Generated::Js(gen) => &gen.js,
+ &self.generated.js
+ }
+
+ pub fn ts(&self) -> Option<&str> {
+ if self.generated.typescript {
+ Some(&self.generated.ts)
+ } else {
+ None
}
}
+ pub fn start(&self) -> Option<&String> {
+ self.generated.start.as_ref()
+ }
+
+ pub fn snippets(&self) -> &HashMap> {
+ &self.generated.snippets
+ }
+
+ pub fn local_modules(&self) -> &HashMap {
+ &self.generated.local_modules
+ }
+
+ pub fn npm_dependencies(&self) -> &HashMap {
+ &self.generated.npm_dependencies
+ }
+
pub fn wasm(&self) -> &walrus::Module {
&self.module
}
@@ -630,20 +628,14 @@ impl Output {
}
fn _emit(&mut self, out_dir: &Path) -> Result<(), Error> {
- let wasm_name = match &self.generated {
- Generated::InterfaceTypes => self.stem.clone(),
- Generated::Js(_) => format!("{}_bg", self.stem),
- };
+ let wasm_name = format!("{}_bg", self.stem);
let wasm_path = out_dir.join(&wasm_name).with_extension("wasm");
fs::create_dir_all(out_dir)?;
let wasm_bytes = self.module.emit_wasm();
fs::write(&wasm_path, wasm_bytes)
.with_context(|| format!("failed to write `{}`", wasm_path.display()))?;
- let gen = match &self.generated {
- Generated::InterfaceTypes => return Ok(()),
- Generated::Js(gen) => gen,
- };
+ let gen = &self.generated;
// Write out all local JS snippets to the final destination now that
// we've collected them from all the programs.
@@ -664,23 +656,29 @@ impl Output {
.with_context(|| format!("failed to write `{}`", path.display()))?;
}
- if gen.npm_dependencies.len() > 0 {
- let map = gen
- .npm_dependencies
- .iter()
- .map(|(k, v)| (k, &v.1))
- .collect::>();
- let json = serde_json::to_string_pretty(&map)?;
+ let is_genmode_nodemodule = matches!(gen.mode, OutputMode::Node { module: true });
+ if !gen.npm_dependencies.is_empty() || is_genmode_nodemodule {
+ #[derive(serde::Serialize)]
+ struct PackageJson<'a> {
+ #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
+ ty: Option<&'static str>,
+ dependencies: BTreeMap<&'a str, &'a str>,
+ }
+ let pj = PackageJson {
+ ty: is_genmode_nodemodule.then_some("module"),
+ dependencies: gen
+ .npm_dependencies
+ .iter()
+ .map(|(k, v)| (k.as_str(), v.1.as_str()))
+ .collect(),
+ };
+ let json = serde_json::to_string_pretty(&pj)?;
fs::write(out_dir.join("package.json"), json)?;
}
// And now that we've got all our JS and TypeScript, actually write it
// out to the filesystem.
- let extension = if gen.mode.nodejs_experimental_modules() {
- "mjs"
- } else {
- "js"
- };
+ let extension = "js";
fn write
(path: P, contents: C) -> Result<(), anyhow::Error>
where
@@ -698,15 +696,27 @@ impl Output {
let start = gen.start.as_deref().unwrap_or("");
- write(
- &js_path,
- format!(
- "import * as wasm from \"./{}.wasm\";\nexport * from \"./{}\";{}",
- wasm_name, js_name, start
- ),
- )?;
-
- write(&out_dir.join(&js_name), reset_indentation(&gen.js))?;
+ if matches!(gen.mode, OutputMode::Node { .. }) {
+ write(
+ &js_path,
+ format!(
+ "\
+{start}
+export * from \"./{js_name}\";",
+ ),
+ )?;
+ } else {
+ write(
+ &js_path,
+ format!(
+ "\
+import * as wasm from \"./{wasm_name}.wasm\";
+export * from \"./{js_name}\";
+{start}"
+ ),
+ )?;
+ }
+ write(out_dir.join(&js_name), reset_indentation(&gen.js))?;
} else {
write(&js_path, reset_indentation(&gen.js))?;
}
@@ -730,8 +740,8 @@ impl Output {
fn gc_module_and_adapters(module: &mut Module) {
loop {
- // Fist up, cleanup the native wasm module. Note that roots can come
- // from custom sections, namely our wasm interface types custom section
+ // Fist up, cleanup the native Wasm module. Note that roots can come
+ // from custom sections, namely our Wasm interface types custom section
// as well as the aux section.
walrus::passes::gc::run(module);
diff --git a/crates/cli-support/src/multivalue.rs b/crates/cli-support/src/multivalue.rs
index 601b2bf5a51..cc0194a6aea 100644
--- a/crates/cli-support/src/multivalue.rs
+++ b/crates/cli-support/src/multivalue.rs
@@ -1,7 +1,8 @@
use crate::wit::{Adapter, NonstandardWitSection};
use crate::wit::{AdapterKind, Instruction, WasmBindgenAux};
use anyhow::{anyhow, Error};
-use walrus::Module;
+use walrus::ir::Value;
+use walrus::{ConstExpr, FunctionId, Module};
use wasm_bindgen_multi_value_xform as multi_value_xform;
use wasm_bindgen_wasm_conventions as wasm_conventions;
@@ -13,30 +14,31 @@ pub fn run(module: &mut Module) -> Result<(), Error> {
let mut to_xform = Vec::new();
let mut slots = Vec::new();
- for (_, adapter) in adapters.adapters.iter_mut() {
+ for adapter in adapters.adapters.values_mut() {
extract_xform(module, adapter, &mut to_xform, &mut slots);
}
if to_xform.is_empty() {
- // Early exit to avoid failing if we don't have a memory or shadow stack
+ // Early exit to avoid failing if we don't have a memory or stack
// pointer because this is a minimal module that doesn't use linear
// memory.
module.customs.add(*adapters);
return Ok(());
}
- let shadow_stack_pointer = module
+ let stack_pointer = module
.customs
.get_typed::()
.expect("aux section should be present")
- .shadow_stack_pointer
- .ok_or_else(|| anyhow!("failed to find shadow stack pointer in wasm module"))?;
+ .stack_pointer
+ .ok_or_else(|| anyhow!("failed to find stack pointer in Wasm module"))?;
let memory = wasm_conventions::get_memory(module)?;
- let wrappers = multi_value_xform::run(module, memory, shadow_stack_pointer, &to_xform)?;
+ let wrappers = multi_value_xform::run(module, memory, stack_pointer, &to_xform)?;
for (slot, id) in slots.into_iter().zip(wrappers) {
match slot {
Slot::Id(s) => *s = id,
Slot::Export(e) => module.exports.get_mut(e).item = id.into(),
+ Slot::TableElement(index) => set_table_entry(module, index, id),
}
}
@@ -48,6 +50,7 @@ pub fn run(module: &mut Module) -> Result<(), Error> {
enum Slot<'a> {
Id(&'a mut walrus::FunctionId),
Export(walrus::ExportId),
+ TableElement(u32),
}
fn extract_xform<'a>(
@@ -76,13 +79,13 @@ fn extract_xform<'a>(
});
let slot = instructions
.iter_mut()
- .filter_map(|i| match &mut i.instr {
- Instruction::Standard(wit_walrus::Instruction::CallCore(f)) => Some(Slot::Id(f)),
+ .find_map(|i| match &mut i.instr {
+ Instruction::CallCore(f) => Some(Slot::Id(f)),
Instruction::CallExport(e) => Some(Slot::Export(*e)),
+ Instruction::CallTableElement(index) => Some(Slot::TableElement(*index)),
_ => None,
})
- .next()
- .expect("should have found call-core");
+ .expect("adapter never calls the underlying function");
// LLVM currently always uses the first parameter for the return
// pointer. We hard code that here, since we have no better option.
@@ -92,10 +95,10 @@ fn extract_xform<'a>(
walrus::ExportItem::Function(f) => f,
_ => panic!("found call to non-function export"),
},
+ Slot::TableElement(func_index) => resolve_table_entry(module, *func_index),
};
to_xform.push((id, 0, types));
slots.push(slot);
- return;
}
// If the last instruction is a `StoreRetptr`, then this must be an adapter
@@ -104,3 +107,96 @@ fn extract_xform<'a>(
// FIXME(#1872) handle this
// if let Some(Instruction::StoreRetptr { .. }) = instructions.last() {}
}
+
+/// Resolves an index in the function table to a function ID.
+fn resolve_table_entry(module: &Module, func_index: u32) -> FunctionId {
+ let table_id = module
+ .tables
+ .main_function_table()
+ .ok()
+ .flatten()
+ .expect("there should only be one function table");
+ module
+ .tables
+ .get(table_id)
+ .elem_segments
+ .iter()
+ .find_map(|&id| {
+ let elem = module.elements.get(id);
+ let offset = match elem.kind {
+ walrus::ElementKind::Active { offset, .. } => match offset {
+ ConstExpr::Value(Value::I32(value)) => value as u32,
+ _ => panic!("table offset was not an i32 value"),
+ },
+ _ => panic!("found non-active element section for function table"),
+ };
+
+ let find = |(i, func_id): (usize, Option<&FunctionId>)| {
+ let table_index = i as u32 + offset;
+ if table_index == func_index {
+ func_id.cloned()
+ } else {
+ None
+ }
+ };
+ match &elem.items {
+ walrus::ElementItems::Functions(items) => {
+ items.iter().map(Some).enumerate().find_map(find)
+ }
+ walrus::ElementItems::Expressions(_, items) => items
+ .iter()
+ .map(|expr| {
+ if let ConstExpr::RefFunc(id) = expr {
+ Some(id)
+ } else {
+ None
+ }
+ })
+ .enumerate()
+ .find_map(find),
+ }
+ })
+ .expect("function in function table is not initialized")
+}
+
+/// Changes the function ID at an index in the function table.
+fn set_table_entry(module: &mut Module, func_index: u32, new_id: FunctionId) {
+ let table_id = module
+ .tables
+ .main_function_table()
+ .ok()
+ .flatten()
+ .expect("there should only be one function table");
+ for &id in module.tables.get(table_id).elem_segments.iter() {
+ let elem = module.elements.get_mut(id);
+ let offset = match elem.kind {
+ walrus::ElementKind::Active { offset, .. } => match offset {
+ ConstExpr::Value(Value::I32(value)) => value as u32,
+ _ => panic!("table offset was not an i32 value"),
+ },
+ _ => panic!("found non-active element section for function table"),
+ };
+ match &mut elem.items {
+ walrus::ElementItems::Functions(items) => {
+ items.iter_mut().enumerate().for_each(|(i, func_id)| {
+ let table_index = i as u32 + offset;
+ if table_index == func_index {
+ *func_id = new_id;
+ }
+ })
+ }
+ walrus::ElementItems::Expressions(_, items) => {
+ items.iter_mut().enumerate().for_each(|(i, func_id)| {
+ let table_index = i as u32 + offset;
+ if table_index == func_index {
+ assert!(
+ matches!(func_id, ConstExpr::RefFunc(_)),
+ "didn't find a function at the expected position"
+ );
+ *func_id = ConstExpr::RefFunc(new_id);
+ }
+ })
+ }
+ }
+ }
+}
diff --git a/crates/cli-support/src/throw2unreachable.rs b/crates/cli-support/src/throw2unreachable.rs
deleted file mode 100644
index 6050911b482..00000000000
--- a/crates/cli-support/src/throw2unreachable.rs
+++ /dev/null
@@ -1,87 +0,0 @@
-use crate::intrinsic::Intrinsic;
-use crate::wit::Instruction;
-use crate::wit::{AdapterKind, AuxImport, NonstandardWitSection, WasmBindgenAux};
-use walrus::ir::*;
-use walrus::Module;
-
-/// Runs a small pass over `Module` to replace all calls to the
-/// `__wbindgen_throw` intrinsic with an `unreachable` instruction.
-///
-/// This pass is executed as part of the wasm interface types support. This is
-/// done to support debug mode executables with wasm interface types. Debug mode
-/// executables will use malloc as well as externref intrinsics. These intrinsics
-/// internally, when they fail, abort the instance. This abort is done through
-/// the `__wbindgen_throw` intrinsic in debug mode to provide a hopefully
-/// useful error message. In release mode it's simply an `unreachable`
-/// instruction.
-///
-/// With wasm interface types we can't rely on intrinsics being available, so we
-/// need to do something about this in debug mode. Our answer is to remove calls
-/// to `__wbindgen_throw` and replace them with `unreachable`.
-///
-/// This has the unintended side effect of making the user-visible function
-/// `wasm_bindgen::throw_str` "just work", but that's hoped to get fix with a
-/// split of crates like described in #1841
-pub fn run(module: &mut Module) {
- // Find the adapter ID which is the import for the call to the throw
- // intrinsic.
- let aux = module.customs.get_typed::().unwrap();
- let throw_import = aux.import_map.iter().find(|(_, import)| match import {
- AuxImport::Intrinsic(Intrinsic::Throw) => true,
- _ => false,
- });
- let throw_adapter = match throw_import {
- Some((id, _)) => *id,
- None => return,
- };
-
- // Find the adapter, if any, which calls this intrinsic
- let wit = module.customs.get_typed::().unwrap();
- let adapter_calling_throw = wit.adapters.iter().find(|(_, adapter)| {
- let instrs = match &adapter.kind {
- AdapterKind::Local { instructions } => instructions,
- _ => return false,
- };
- instrs.iter().any(|i| match i.instr {
- Instruction::CallAdapter(a) => a == throw_adapter,
- _ => false,
- })
- });
- let adapter_calling_throw = match adapter_calling_throw {
- Some((id, _)) => *id,
- None => return,
- };
-
- // ... then using the adapter that calls the intrinsic, find which core
- // import in the wasm module it's implementing.
- let import = wit
- .implements
- .iter()
- .find(|(_, _, adapter)| *adapter == adapter_calling_throw);
- let function = match import {
- Some((_, function, _)) => *function,
- None => return,
- };
-
- // .. and now replace all calls to `function` with `unreachable`
- // instructions
- for (_, func) in module.funcs.iter_local_mut() {
- let entry = func.entry_block();
- dfs_pre_order_mut(&mut Rewrite { function }, func, entry);
- }
-
- struct Rewrite {
- function: walrus::FunctionId,
- }
-
- impl VisitorMut for Rewrite {
- fn visit_instr_mut(&mut self, instr: &mut Instr, _: &mut InstrLocId) {
- match instr {
- Instr::Call(c) if c.func == self.function => {
- *instr = Unreachable {}.into();
- }
- _ => {}
- }
- }
- }
-}
diff --git a/crates/cli-support/src/wasm2es6js.rs b/crates/cli-support/src/wasm2es6js.rs
index 094180d343f..c16eb0a4504 100644
--- a/crates/cli-support/src/wasm2es6js.rs
+++ b/crates/cli-support/src/wasm2es6js.rs
@@ -1,4 +1,5 @@
use anyhow::{bail, Error};
+use base64::{prelude::BASE64_STANDARD, Engine as _};
use std::collections::HashSet;
use std::fmt::Write;
use walrus::Module;
@@ -33,7 +34,7 @@ impl Config {
}
pub fn generate(&mut self, wasm: &[u8]) -> Result