Browse Source

Initial version of a JS -> Rust conversion trait. (#2276)

This Pull Request closes #1975. It's still a work in progress, but tries to go in that direction.

It changes the following:

- Adds a new `TryFromJs` trait, that can be derived using a new `boa_derive` crate.
- Adds a new `try_js_into()` function that, similarly to the standard library `TryInto` trait

Things to think about:
- Should the `boa_derive` crate be re-exported in `boa_engine` using a `derive` feature, similar to how it's done in `serde`?
- The current implementation only converts perfectly valid values. So, if we try to convert a big integer into an `i8`, or any floating point number to an `f32`. So, you cannot derive `TryFromJs` for structures that contain an `f32` for example (you can still manually implement the trait, though, and decide in favour of a loss of precision). Should we also provide some traits for transparent loss of precision?
- Currently, you cannot convert between types, so if the JS struct has an integer, you cannot cast it to a boolean, for example. Should we provide a `TryConvertJs` trait, for example to force conversions?
- Currently we only have basic types and object conversions. Should add `Array` to `Vec` conversion, for example, right? Should we also add `TypedArray` conversions? What about `Map` and `Set`? Does this step over the fine grained APIs that we were creating?

Note that this still requires a bunch of documentation, tests, and validation from the dev team and from the users that requested this feature. I'm particularly interested in @lastmjs's thoughts on this API.

I already added an usage example in `boa_examples/src/bin/derive.rs`.

Co-authored-by: jedel1043 <jedel0124@gmail.com>
pull/2769/head
Iban Eguia Moraza 2 years ago
parent
commit
540aa2be4f
  1. 12
      .github/workflows/rust.yml
  2. 233
      Cargo.lock
  3. 11
      Cargo.toml
  4. 54
      boa_engine/src/value/conversions/mod.rs
  5. 0
      boa_engine/src/value/conversions/serde_json.rs
  6. 237
      boa_engine/src/value/conversions/try_from_js.rs
  7. 19
      boa_engine/src/value/mod.rs
  8. 2
      boa_examples/Cargo.toml
  9. 48
      boa_examples/src/bin/derive.rs
  10. 2
      boa_icu_provider/Cargo.toml
  11. 1
      boa_macros/Cargo.toml
  12. 147
      boa_macros/src/lib.rs
  13. 0
      boa_macros/tests/tests.rs
  14. 16
      boa_macros_tests/Cargo.toml
  15. 20
      boa_macros_tests/tests/derive/from_js_with.rs
  16. 8
      boa_macros_tests/tests/derive/simple_struct.rs
  17. 6
      boa_macros_tests/tests/tests.rs

12
.github/workflows/rust.yml

@ -22,14 +22,18 @@ jobs:
toolchain: stable toolchain: stable
override: true override: true
profile: minimal profile: minimal
- uses: actions-rs/install@v0.1
with:
crate: cargo-tarpaulin
version: latest
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
with: with:
key: tarpaulin key: tarpaulin
- name: Run cargo-tarpaulin - name: Run tarpaulin
uses: actions-rs/tarpaulin@v0.1 uses: actions-rs/cargo@v1
with: with:
version: 0.22.0 command: tarpaulin
args: --features intl --ignore-tests --engine llvm args: --workspace --features intl --ignore-tests --engine llvm --out Xml
- name: Upload to codecov.io - name: Upload to codecov.io
uses: codecov/codecov-action@v3 uses: codecov/codecov-action@v3

233
Cargo.lock generated

@ -160,22 +160,22 @@ dependencies = [
[[package]] [[package]]
name = "async-io" name = "async-io"
version = "1.12.0" version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c374dda1ed3e7d8f0d9ba58715f924862c63eae6849c92d3a18e7fbde9e2794" checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af"
dependencies = [ dependencies = [
"async-lock", "async-lock",
"autocfg", "autocfg",
"cfg-if 1.0.0",
"concurrent-queue", "concurrent-queue",
"futures-lite", "futures-lite",
"libc",
"log", "log",
"parking", "parking",
"polling", "polling",
"rustix",
"slab", "slab",
"socket2", "socket2",
"waker-fn", "waker-fn",
"windows-sys 0.42.0",
] ]
[[package]] [[package]]
@ -219,9 +219,9 @@ dependencies = [
[[package]] [[package]]
name = "async-task" name = "async-task"
version = "4.3.0" version = "4.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae"
[[package]] [[package]]
name = "atomic-polyfill" name = "atomic-polyfill"
@ -291,6 +291,15 @@ version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
[[package]]
name = "basic-toml"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c0de75129aa8d0cceaf750b89013f0e08804d6ec61416da787b35ad0d7cddf1"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "bincode" name = "bincode"
version = "1.3.3" version = "1.3.3"
@ -479,6 +488,15 @@ dependencies = [
"synstructure 0.13.0", "synstructure 0.13.0",
] ]
[[package]]
name = "boa_macros_tests"
version = "0.16.0"
dependencies = [
"boa_engine",
"boa_macros",
"trybuild",
]
[[package]] [[package]]
name = "boa_parser" name = "boa_parser"
version = "0.16.0" version = "0.16.0"
@ -741,7 +759,7 @@ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
"bitflags 1.3.2", "bitflags 1.3.2",
"clap_lex 0.4.0", "clap_lex 0.4.1",
"strsim 0.10.0", "strsim 0.10.0",
] ]
@ -768,9 +786,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_lex" name = "clap_lex"
version = "0.4.0" version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f0807fb6f644c83f3e4ec014fec9858c1c8b26a7db8eb5f0bde5817df9c1df7" checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1"
[[package]] [[package]]
name = "clipboard-win" name = "clipboard-win"
@ -922,9 +940,9 @@ dependencies = [
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.5" version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@ -1066,9 +1084,9 @@ dependencies = [
[[package]] [[package]]
name = "cxx" name = "cxx"
version = "1.0.93" version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9c00419335c41018365ddf7e4d5f1c12ee3659ddcf3e01974650ba1de73d038" checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93"
dependencies = [ dependencies = [
"cc", "cc",
"cxxbridge-flags", "cxxbridge-flags",
@ -1078,9 +1096,9 @@ dependencies = [
[[package]] [[package]]
name = "cxx-build" name = "cxx-build"
version = "1.0.93" version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb8307ad413a98fff033c8545ecf133e3257747b3bae935e7602aab8aa92d4ca" checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b"
dependencies = [ dependencies = [
"cc", "cc",
"codespan-reporting", "codespan-reporting",
@ -1093,15 +1111,15 @@ dependencies = [
[[package]] [[package]]
name = "cxxbridge-flags" name = "cxxbridge-flags"
version = "1.0.93" version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edc52e2eb08915cb12596d29d55f0b5384f00d697a646dbd269b6ecb0fbd9d31" checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb"
[[package]] [[package]]
name = "cxxbridge-macro" name = "cxxbridge-macro"
version = "1.0.93" version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "631569015d0d8d54e6c241733f944042623ab6df7bc3be7466874b05fcdb1c5f" checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1377,13 +1395,13 @@ dependencies = [
[[package]] [[package]]
name = "errno" name = "errno"
version = "0.2.8" version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0"
dependencies = [ dependencies = [
"errno-dragonfly", "errno-dragonfly",
"libc", "libc",
"winapi", "windows-sys 0.45.0",
] ]
[[package]] [[package]]
@ -1445,9 +1463,9 @@ dependencies = [
[[package]] [[package]]
name = "fd-lock" name = "fd-lock"
version = "3.0.10" version = "3.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ef1a30ae415c3a691a4f41afddc2dbcd6d70baf338368d85ebc1e8ed92cedb9" checksum = "9799aefb4a2e4a01cc47610b1dd47c18ab13d991f27bbcaed9296f5a53d5cbad"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"rustix", "rustix",
@ -1462,7 +1480,7 @@ checksum = "8a3de6e8d11b22ff9edc6d916f890800597d60f8b2da1caf2955c274638d6412"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"libc", "libc",
"redox_syscall", "redox_syscall 0.2.16",
"windows-sys 0.45.0", "windows-sys 0.45.0",
] ]
@ -1631,9 +1649,9 @@ dependencies = [
[[package]] [[package]]
name = "generic-array" name = "generic-array"
version = "0.14.6" version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [ dependencies = [
"typenum", "typenum",
"version_check", "version_check",
@ -1853,16 +1871,16 @@ dependencies = [
[[package]] [[package]]
name = "iana-time-zone" name = "iana-time-zone"
version = "0.1.53" version = "0.1.55"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" checksum = "716f12fbcfac6ffab0a5e9ec51d0a0ff70503742bb2dc7b99396394c9dc323f0"
dependencies = [ dependencies = [
"android_system_properties", "android_system_properties",
"core-foundation-sys", "core-foundation-sys",
"iana-time-zone-haiku", "iana-time-zone-haiku",
"js-sys", "js-sys",
"wasm-bindgen", "wasm-bindgen",
"winapi", "windows",
] ]
[[package]] [[package]]
@ -2380,15 +2398,15 @@ dependencies = [
[[package]] [[package]]
name = "ipnet" name = "ipnet"
version = "2.7.1" version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f"
[[package]] [[package]]
name = "is-terminal" name = "is-terminal"
version = "0.4.5" version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8687c819457e979cc940d09cb16e42a1bf70aa6b60a549de6d3a62a0ee90c69e" checksum = "256017f749ab3117e93acb91063009e1f1bb56d03965b14c2c8df4eb02c524d8"
dependencies = [ dependencies = [
"hermit-abi 0.3.1", "hermit-abi 0.3.1",
"io-lifetimes", "io-lifetimes",
@ -2485,9 +2503,9 @@ dependencies = [
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.1.4" version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" checksum = "cd550e73688e6d578f0ac2119e32b797a327631a42f9433e59d02e139c8df60d"
[[package]] [[package]]
name = "litemap" name = "litemap"
@ -2925,7 +2943,7 @@ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"instant", "instant",
"libc", "libc",
"redox_syscall", "redox_syscall 0.2.16",
"smallvec", "smallvec",
"winapi", "winapi",
] ]
@ -2938,7 +2956,7 @@ checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"libc", "libc",
"redox_syscall", "redox_syscall 0.2.16",
"smallvec", "smallvec",
"windows-sys 0.45.0", "windows-sys 0.45.0",
] ]
@ -3240,6 +3258,15 @@ dependencies = [
"bitflags 1.3.2", "bitflags 1.3.2",
] ]
[[package]]
name = "redox_syscall"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
dependencies = [
"bitflags 1.3.2",
]
[[package]] [[package]]
name = "redox_users" name = "redox_users"
version = "0.4.3" version = "0.4.3"
@ -3247,7 +3274,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
dependencies = [ dependencies = [
"getrandom", "getrandom",
"redox_syscall", "redox_syscall 0.2.16",
"thiserror", "thiserror",
] ]
@ -3311,9 +3338,9 @@ dependencies = [
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.11.15" version = "0.11.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ba30cc2c0cd02af1222ed216ba659cdb2f879dfe3181852fe7c50b1d0005949" checksum = "27b71749df584b7f4cac2c426c127a7c785a5106cc98f7a8feb044115f0fa254"
dependencies = [ dependencies = [
"base64", "base64",
"bytes", "bytes",
@ -3382,9 +3409,9 @@ dependencies = [
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.21" version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" checksum = "d4a36c42d1873f9a77c53bde094f9664d9891bc604a45b4798fd2c389ed12e5b"
[[package]] [[package]]
name = "rustc-hash" name = "rustc-hash"
@ -3412,9 +3439,9 @@ dependencies = [
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.36.11" version = "0.37.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db4165c9963ab29e422d6c26fbc1d37f15bace6b2810221f9d925023480fcf0e" checksum = "0e78cc525325c06b4a7ff02db283472f3c042b7ff0c391f96c6d5ac6f4f91b75"
dependencies = [ dependencies = [
"bitflags 1.3.2", "bitflags 1.3.2",
"errno", "errno",
@ -3817,9 +3844,9 @@ dependencies = [
[[package]] [[package]]
name = "spin" name = "spin"
version = "0.9.6" version = "0.9.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5d6e0250b93c8427a177b849d144a96d5acc57006149479403d7861ab721e34" checksum = "c0959fd6f767df20b231736396e4f602171e00d95205676286e79d4a4eb67bef"
dependencies = [ dependencies = [
"lock_api", "lock_api",
] ]
@ -4031,15 +4058,15 @@ checksum = "8ae9980cab1db3fceee2f6c6f643d5d8de2997c58ee8d25fb0cc8a9e9e7348e5"
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.4.0" version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"fastrand", "fastrand",
"redox_syscall", "redox_syscall 0.3.5",
"rustix", "rustix",
"windows-sys 0.42.0", "windows-sys 0.45.0",
] ]
[[package]] [[package]]
@ -4230,14 +4257,13 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.26.0" version = "1.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64" checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"bytes", "bytes",
"libc", "libc",
"memchr",
"mio", "mio",
"num_cpus", "num_cpus",
"pin-project-lite", "pin-project-lite",
@ -4301,9 +4327,9 @@ dependencies = [
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
version = "0.19.7" version = "0.19.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc18466501acd8ac6a3f615dd29a3438f8ca6bb3b19537138b3106e575621274" checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"serde", "serde",
@ -4379,6 +4405,21 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
[[package]]
name = "trybuild"
version = "1.0.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501dbdbb99861e4ab6b60eb6a7493956a9defb644fd034bc4a5ef27c693c8a3a"
dependencies = [
"basic-toml",
"glob",
"once_cell",
"serde",
"serde_derive",
"serde_json",
"termcolor",
]
[[package]] [[package]]
name = "typenum" name = "typenum"
version = "1.16.0" version = "1.16.0"
@ -4931,6 +4972,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2649ff315bee4c98757f15dac226efe3d81927adbb6e882084bb1ee3e0c330a7"
dependencies = [
"windows-targets 0.47.0",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.33.0" version = "0.33.0"
@ -4950,12 +5000,12 @@ version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [ dependencies = [
"windows_aarch64_gnullvm", "windows_aarch64_gnullvm 0.42.2",
"windows_aarch64_msvc 0.42.2", "windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2", "windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2", "windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2", "windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm", "windows_x86_64_gnullvm 0.42.2",
"windows_x86_64_msvc 0.42.2", "windows_x86_64_msvc 0.42.2",
] ]
@ -4965,7 +5015,7 @@ version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [ dependencies = [
"windows-targets", "windows-targets 0.42.2",
] ]
[[package]] [[package]]
@ -4974,21 +5024,42 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [ dependencies = [
"windows_aarch64_gnullvm", "windows_aarch64_gnullvm 0.42.2",
"windows_aarch64_msvc 0.42.2", "windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2", "windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2", "windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2", "windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm", "windows_x86_64_gnullvm 0.42.2",
"windows_x86_64_msvc 0.42.2", "windows_x86_64_msvc 0.42.2",
] ]
[[package]]
name = "windows-targets"
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f8996d3f43b4b2d44327cd71b7b0efd1284ab60e6e9d0e8b630e18555d87d3e"
dependencies = [
"windows_aarch64_gnullvm 0.47.0",
"windows_aarch64_msvc 0.47.0",
"windows_i686_gnu 0.47.0",
"windows_i686_msvc 0.47.0",
"windows_x86_64_gnu 0.47.0",
"windows_x86_64_gnullvm 0.47.0",
"windows_x86_64_msvc 0.47.0",
]
[[package]] [[package]]
name = "windows_aarch64_gnullvm" name = "windows_aarch64_gnullvm"
version = "0.42.2" version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "831d567d53d4f3cb1db332b68e6e2b6260228eb4d99a777d8b2e8ed794027c90"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.33.0" version = "0.33.0"
@ -5001,6 +5072,12 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_aarch64_msvc"
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a42d54a417c60ce4f0e31661eed628f0fa5aca73448c093ec4d45fab4c51cdf"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.33.0" version = "0.33.0"
@ -5013,6 +5090,12 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_gnu"
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1925beafdbb22201a53a483db861a5644123157c1c3cee83323a2ed565d71e3"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.33.0" version = "0.33.0"
@ -5025,6 +5108,12 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_i686_msvc"
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a8ef8f2f1711b223947d9b69b596cf5a4e452c930fb58b6fc3fdae7d0ec6b31"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.33.0" version = "0.33.0"
@ -5037,12 +5126,24 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]]
name = "windows_x86_64_gnu"
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7acaa0c2cf0d2ef99b61c308a0c3dbae430a51b7345dedec470bd8f53f5a3642"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.42.2" version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5a0628f71be1d11e17ca4a0e9e15b3a5180f6fbf1c2d55e3ba3f850378052c1"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.33.0" version = "0.33.0"
@ -5055,11 +5156,17 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]]
name = "windows_x86_64_msvc"
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d6e62c256dc6d40b8c8707df17df8d774e60e39db723675241e7c15e910bce7"
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.3.6" version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23d020b441f92996c80d94ae9166e8501e59c7bb56121189dc9eab3bd8216966" checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]

11
Cargo.toml

@ -1,18 +1,19 @@
[workspace] [workspace]
members = [ members = [
"boa_ast",
"boa_cli", "boa_cli",
"boa_engine", "boa_engine",
"boa_ast", "boa_examples",
"boa_parser",
"boa_gc", "boa_gc",
"boa_icu_provider",
"boa_interner", "boa_interner",
"boa_macros",
"boa_macros_tests",
"boa_parser",
"boa_profiler", "boa_profiler",
"boa_tester", "boa_tester",
"boa_unicode", "boa_unicode",
"boa_wasm", "boa_wasm",
"boa_examples",
"boa_macros",
"boa_icu_provider",
] ]
[workspace.package] [workspace.package]

54
boa_engine/src/value/conversions.rs → boa_engine/src/value/conversions/mod.rs

@ -1,5 +1,10 @@
//! Conversions from JavaScript values into Rust values, and the other way around.
use super::{JsBigInt, JsObject, JsString, JsSymbol, JsValue, Profiler}; use super::{JsBigInt, JsObject, JsString, JsSymbol, JsValue, Profiler};
mod serde_json;
pub(super) mod try_from_js;
impl<T> From<T> for JsValue impl<T> From<T> for JsValue
where where
T: Into<JsString>, T: Into<JsString>,
@ -14,6 +19,8 @@ where
impl From<char> for JsValue { impl From<char> for JsValue {
#[inline] #[inline]
fn from(value: char) -> Self { fn from(value: char) -> Self {
let _timer = Profiler::global().start_event("From<char>", "value");
Self::new(value.to_string()) Self::new(value.to_string())
} }
} }
@ -21,37 +28,35 @@ impl From<char> for JsValue {
impl From<JsSymbol> for JsValue { impl From<JsSymbol> for JsValue {
#[inline] #[inline]
fn from(value: JsSymbol) -> Self { fn from(value: JsSymbol) -> Self {
let _timer = Profiler::global().start_event("From<JsSymbol>", "value");
Self::Symbol(value) Self::Symbol(value)
} }
} }
impl From<f32> for JsValue { impl From<f32> for JsValue {
#[allow(clippy::float_cmp)]
#[inline] #[inline]
fn from(value: f32) -> Self { fn from(value: f32) -> Self {
// if value as i32 as f64 == value { let _timer = Profiler::global().start_event("From<f32>", "value");
// Self::Integer(value as i32)
// } else {
Self::Rational(value.into()) Self::Rational(value.into())
// }
} }
} }
impl From<f64> for JsValue { impl From<f64> for JsValue {
#[allow(clippy::float_cmp)]
#[inline] #[inline]
fn from(value: f64) -> Self { fn from(value: f64) -> Self {
// if value as i32 as f64 == value { let _timer = Profiler::global().start_event("From<f64>", "value");
// Self::Integer(value as i32)
// } else {
Self::Rational(value) Self::Rational(value)
// }
} }
} }
impl From<u8> for JsValue { impl From<u8> for JsValue {
#[inline] #[inline]
fn from(value: u8) -> Self { fn from(value: u8) -> Self {
let _timer = Profiler::global().start_event("From<u8>", "value");
Self::Integer(value.into()) Self::Integer(value.into())
} }
} }
@ -59,6 +64,8 @@ impl From<u8> for JsValue {
impl From<i8> for JsValue { impl From<i8> for JsValue {
#[inline] #[inline]
fn from(value: i8) -> Self { fn from(value: i8) -> Self {
let _timer = Profiler::global().start_event("From<i8>", "value");
Self::Integer(value.into()) Self::Integer(value.into())
} }
} }
@ -66,6 +73,8 @@ impl From<i8> for JsValue {
impl From<u16> for JsValue { impl From<u16> for JsValue {
#[inline] #[inline]
fn from(value: u16) -> Self { fn from(value: u16) -> Self {
let _timer = Profiler::global().start_event("From<u16>", "value");
Self::Integer(value.into()) Self::Integer(value.into())
} }
} }
@ -73,6 +82,8 @@ impl From<u16> for JsValue {
impl From<i16> for JsValue { impl From<i16> for JsValue {
#[inline] #[inline]
fn from(value: i16) -> Self { fn from(value: i16) -> Self {
let _timer = Profiler::global().start_event("From<i16>", "value");
Self::Integer(value.into()) Self::Integer(value.into())
} }
} }
@ -80,6 +91,8 @@ impl From<i16> for JsValue {
impl From<u32> for JsValue { impl From<u32> for JsValue {
#[inline] #[inline]
fn from(value: u32) -> Self { fn from(value: u32) -> Self {
let _timer = Profiler::global().start_event("From<u32>", "value");
i32::try_from(value).map_or_else(|_| Self::Rational(value.into()), Self::Integer) i32::try_from(value).map_or_else(|_| Self::Rational(value.into()), Self::Integer)
} }
} }
@ -87,6 +100,8 @@ impl From<u32> for JsValue {
impl From<i32> for JsValue { impl From<i32> for JsValue {
#[inline] #[inline]
fn from(value: i32) -> Self { fn from(value: i32) -> Self {
let _timer = Profiler::global().start_event("From<i32>", "value");
Self::Integer(value) Self::Integer(value)
} }
} }
@ -94,6 +109,8 @@ impl From<i32> for JsValue {
impl From<JsBigInt> for JsValue { impl From<JsBigInt> for JsValue {
#[inline] #[inline]
fn from(value: JsBigInt) -> Self { fn from(value: JsBigInt) -> Self {
let _timer = Profiler::global().start_event("From<JsBigInt>", "value");
Self::BigInt(value) Self::BigInt(value)
} }
} }
@ -101,6 +118,8 @@ impl From<JsBigInt> for JsValue {
impl From<usize> for JsValue { impl From<usize> for JsValue {
#[inline] #[inline]
fn from(value: usize) -> Self { fn from(value: usize) -> Self {
let _timer = Profiler::global().start_event("From<usize>", "value");
i32::try_from(value).map_or(Self::Rational(value as f64), Self::Integer) i32::try_from(value).map_or(Self::Rational(value as f64), Self::Integer)
} }
} }
@ -108,6 +127,8 @@ impl From<usize> for JsValue {
impl From<u64> for JsValue { impl From<u64> for JsValue {
#[inline] #[inline]
fn from(value: u64) -> Self { fn from(value: u64) -> Self {
let _timer = Profiler::global().start_event("From<u64>", "value");
i32::try_from(value).map_or(Self::Rational(value as f64), Self::Integer) i32::try_from(value).map_or(Self::Rational(value as f64), Self::Integer)
} }
} }
@ -115,6 +136,8 @@ impl From<u64> for JsValue {
impl From<i64> for JsValue { impl From<i64> for JsValue {
#[inline] #[inline]
fn from(value: i64) -> Self { fn from(value: i64) -> Self {
let _timer = Profiler::global().start_event("From<i64>", "value");
i32::try_from(value).map_or(Self::Rational(value as f64), Self::Integer) i32::try_from(value).map_or(Self::Rational(value as f64), Self::Integer)
} }
} }
@ -122,6 +145,8 @@ impl From<i64> for JsValue {
impl From<bool> for JsValue { impl From<bool> for JsValue {
#[inline] #[inline]
fn from(value: bool) -> Self { fn from(value: bool) -> Self {
let _timer = Profiler::global().start_event("From<bool>", "value");
Self::Boolean(value) Self::Boolean(value)
} }
} }
@ -130,6 +155,7 @@ impl From<JsObject> for JsValue {
#[inline] #[inline]
fn from(object: JsObject) -> Self { fn from(object: JsObject) -> Self {
let _timer = Profiler::global().start_event("From<JsObject>", "value"); let _timer = Profiler::global().start_event("From<JsObject>", "value");
Self::Object(object) Self::Object(object)
} }
} }
@ -137,11 +163,18 @@ impl From<JsObject> for JsValue {
impl From<()> for JsValue { impl From<()> for JsValue {
#[inline] #[inline]
fn from(_: ()) -> Self { fn from(_: ()) -> Self {
let _timer = Profiler::global().start_event("From<()>", "value");
Self::null() Self::null()
} }
} }
/// Converts an `Option<T>` into a `JsValue`.
///
/// It will convert the `None` variant to `JsValue::undefined()`, and the `Some()` variant into a
/// `JsValue` using the `Into` trait.
pub(crate) trait IntoOrUndefined { pub(crate) trait IntoOrUndefined {
/// Converts an `Option<T>` into a `JsValue`.
fn into_or_undefined(self) -> JsValue; fn into_or_undefined(self) -> JsValue;
} }
@ -149,6 +182,7 @@ impl<T> IntoOrUndefined for Option<T>
where where
T: Into<JsValue>, T: Into<JsValue>,
{ {
#[inline]
fn into_or_undefined(self) -> JsValue { fn into_or_undefined(self) -> JsValue {
self.map_or_else(JsValue::undefined, Into::into) self.map_or_else(JsValue::undefined, Into::into)
} }

0
boa_engine/src/value/serde_json.rs → boa_engine/src/value/conversions/serde_json.rs

237
boa_engine/src/value/conversions/try_from_js.rs

@ -0,0 +1,237 @@
//! This module contains the [`TryFromJs`] trait, and conversions to basic Rust types.
use crate::{Context, JsBigInt, JsNativeError, JsResult, JsValue};
use num_bigint::BigInt;
/// This trait adds a fallible and efficient conversions from a [`JsValue`] to Rust types.
pub trait TryFromJs: Sized {
/// This function tries to convert a JavaScript value into `Self`.
fn try_from_js(value: &JsValue, context: &mut Context<'_>) -> JsResult<Self>;
}
impl JsValue {
/// This function is the inverse of [`TryFromJs`]. It tries to convert a [`JsValue`] to a given
/// Rust type.
pub fn try_js_into<T>(&self, context: &mut Context<'_>) -> JsResult<T>
where
T: TryFromJs,
{
T::try_from_js(self, context)
}
}
impl TryFromJs for bool {
fn try_from_js(value: &JsValue, _context: &mut Context<'_>) -> JsResult<Self> {
match value {
JsValue::Boolean(b) => Ok(*b),
_ => Err(JsNativeError::typ()
.with_message("cannot convert value to a boolean")
.into()),
}
}
}
impl TryFromJs for String {
fn try_from_js(value: &JsValue, _context: &mut Context<'_>) -> JsResult<Self> {
match value {
JsValue::String(s) => s.to_std_string().map_err(|e| {
JsNativeError::typ()
.with_message(format!("could not convert JsString to Rust string, since it has UTF-16 characters: {e}"))
.into()
}),
_ => Err(JsNativeError::typ()
.with_message("cannot convert value to a String")
.into()),
}
}
}
impl<T> TryFromJs for Option<T>
where
T: TryFromJs,
{
fn try_from_js(value: &JsValue, context: &mut Context<'_>) -> JsResult<Self> {
match value {
JsValue::Null | JsValue::Undefined => Ok(None),
value => Ok(Some(T::try_from_js(value, context)?)),
}
}
}
impl TryFromJs for JsBigInt {
fn try_from_js(value: &JsValue, _context: &mut Context<'_>) -> JsResult<Self> {
match value {
JsValue::BigInt(b) => Ok(b.clone()),
_ => Err(JsNativeError::typ()
.with_message("cannot convert value to a BigInt")
.into()),
}
}
}
impl TryFromJs for BigInt {
fn try_from_js(value: &JsValue, _context: &mut Context<'_>) -> JsResult<Self> {
match value {
JsValue::BigInt(b) => Ok(b.as_inner().clone()),
_ => Err(JsNativeError::typ()
.with_message("cannot convert value to a BigInt")
.into()),
}
}
}
impl TryFromJs for JsValue {
fn try_from_js(value: &JsValue, _context: &mut Context<'_>) -> JsResult<Self> {
Ok(value.clone())
}
}
impl TryFromJs for f64 {
fn try_from_js(value: &JsValue, _context: &mut Context<'_>) -> JsResult<Self> {
match value {
JsValue::Integer(i) => Ok((*i).into()),
JsValue::Rational(r) => Ok(*r),
_ => Err(JsNativeError::typ()
.with_message("cannot convert value to a f64")
.into()),
}
}
}
impl TryFromJs for i8 {
fn try_from_js(value: &JsValue, _context: &mut Context<'_>) -> JsResult<Self> {
match value {
JsValue::Integer(i) => (*i).try_into().map_err(|e| {
JsNativeError::typ()
.with_message(format!("cannot convert value to a i8: {e}"))
.into()
}),
_ => Err(JsNativeError::typ()
.with_message("cannot convert value to a i8")
.into()),
}
}
}
impl TryFromJs for u8 {
fn try_from_js(value: &JsValue, _context: &mut Context<'_>) -> JsResult<Self> {
match value {
JsValue::Integer(i) => (*i).try_into().map_err(|e| {
JsNativeError::typ()
.with_message(format!("cannot convert value to a u8: {e}"))
.into()
}),
_ => Err(JsNativeError::typ()
.with_message("cannot convert value to a u8")
.into()),
}
}
}
impl TryFromJs for i16 {
fn try_from_js(value: &JsValue, _context: &mut Context<'_>) -> JsResult<Self> {
match value {
JsValue::Integer(i) => (*i).try_into().map_err(|e| {
JsNativeError::typ()
.with_message(format!("cannot convert value to a i16: {e}"))
.into()
}),
_ => Err(JsNativeError::typ()
.with_message("cannot convert value to a i16")
.into()),
}
}
}
impl TryFromJs for u16 {
fn try_from_js(value: &JsValue, _context: &mut Context<'_>) -> JsResult<Self> {
match value {
JsValue::Integer(i) => (*i).try_into().map_err(|e| {
JsNativeError::typ()
.with_message(format!("cannot convert value to a iu16: {e}"))
.into()
}),
_ => Err(JsNativeError::typ()
.with_message("cannot convert value to a u16")
.into()),
}
}
}
impl TryFromJs for i32 {
fn try_from_js(value: &JsValue, _context: &mut Context<'_>) -> JsResult<Self> {
match value {
JsValue::Integer(i) => Ok(*i),
_ => Err(JsNativeError::typ()
.with_message("cannot convert value to a i32")
.into()),
}
}
}
impl TryFromJs for u32 {
fn try_from_js(value: &JsValue, _context: &mut Context<'_>) -> JsResult<Self> {
match value {
JsValue::Integer(i) => (*i).try_into().map_err(|e| {
JsNativeError::typ()
.with_message(format!("cannot convert value to a u32: {e}"))
.into()
}),
_ => Err(JsNativeError::typ()
.with_message("cannot convert value to a u32")
.into()),
}
}
}
impl TryFromJs for i64 {
fn try_from_js(value: &JsValue, _context: &mut Context<'_>) -> JsResult<Self> {
match value {
JsValue::Integer(i) => Ok((*i).into()),
_ => Err(JsNativeError::typ()
.with_message("cannot convert value to a i64")
.into()),
}
}
}
impl TryFromJs for u64 {
fn try_from_js(value: &JsValue, _context: &mut Context<'_>) -> JsResult<Self> {
match value {
JsValue::Integer(i) => (*i).try_into().map_err(|e| {
JsNativeError::typ()
.with_message(format!("cannot convert value to a u64: {e}"))
.into()
}),
_ => Err(JsNativeError::typ()
.with_message("cannot convert value to a u64")
.into()),
}
}
}
impl TryFromJs for i128 {
fn try_from_js(value: &JsValue, _context: &mut Context<'_>) -> JsResult<Self> {
match value {
JsValue::Integer(i) => Ok((*i).into()),
_ => Err(JsNativeError::typ()
.with_message("cannot convert value to a i128")
.into()),
}
}
}
impl TryFromJs for u128 {
fn try_from_js(value: &JsValue, _context: &mut Context<'_>) -> JsResult<Self> {
match value {
JsValue::Integer(i) => (*i).try_into().map_err(|e| {
JsNativeError::typ()
.with_message(format!("cannot convert value to a u128: {e}"))
.into()
}),
_ => Err(JsNativeError::typ()
.with_message("cannot convert value to a u128")
.into()),
}
}
}

19
boa_engine/src/value/mod.rs

@ -3,15 +3,13 @@
//! Javascript values, utility methods and conversion between Javascript values and Rust values. //! Javascript values, utility methods and conversion between Javascript values and Rust values.
mod conversions; mod conversions;
pub(crate) mod display;
mod equality; mod equality;
mod hash; mod hash;
mod integer; mod integer;
mod operations; mod operations;
mod serde_json;
mod r#type; mod r#type;
pub(crate) mod display;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
@ -39,14 +37,15 @@ use std::{
ops::Sub, ops::Sub,
}; };
pub(crate) use conversions::*; #[doc(inline)]
pub use self::{
pub use display::ValueDisplay; conversions::try_from_js::TryFromJs, display::ValueDisplay, integer::IntegerOrInfinity,
pub use integer::IntegerOrInfinity; operations::*, r#type::Type,
pub use operations::*; };
pub use r#type::Type; #[doc(inline)]
pub use boa_macros::TryFromJs;
pub(crate) use self::integer::IntegerOrNan; pub(crate) use self::{conversions::IntoOrUndefined, integer::IntegerOrNan};
static TWO_E_64: Lazy<BigInt> = Lazy::new(|| { static TWO_E_64: Lazy<BigInt> = Lazy::new(|| {
const TWO_E_64: u128 = 2u128.pow(64); const TWO_E_64: u128 = 2u128.pow(64);

2
boa_examples/Cargo.toml

@ -9,8 +9,6 @@ license.workspace = true
repository.workspace = true repository.workspace = true
rust-version.workspace = true rust-version.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
boa_engine = { workspace = true, features = ["console"] } boa_engine = { workspace = true, features = ["console"] }
boa_ast.workspace = true boa_ast.workspace = true

48
boa_examples/src/bin/derive.rs

@ -0,0 +1,48 @@
use boa_engine::{value::TryFromJs, Context, JsNativeError, JsResult, JsValue, Source};
/// You can easily derive `TryFromJs` for structures with base Rust types.
///
/// By default, the conversion will only work if the type is directly representable by the Rust
/// type.
#[derive(Debug, TryFromJs)]
#[allow(dead_code)]
struct TestStruct {
inner: bool,
hello: String,
// You can override the conversion of an attribute.
#[boa(from_js_with = "lossy_conversion")]
my_float: i16,
}
fn main() {
let js_str = r#"
let x = {
inner: false,
hello: "World",
my_float: 2.9,
};
x;
"#;
let js = Source::from_bytes(js_str);
let mut context = Context::default();
let res = context.eval_script(js).unwrap();
let str = TestStruct::try_from_js(&res, &mut context)
.map_err(|e| e.to_string())
.unwrap();
println!("{str:?}");
}
/// Converts the value lossly
fn lossy_conversion(value: &JsValue, _context: &mut Context) -> JsResult<i16> {
match value {
JsValue::Rational(r) => Ok(r.round() as i16),
JsValue::Integer(i) => Ok(*i as i16),
_ => Err(JsNativeError::typ()
.with_message("cannot convert value to an i16")
.into()),
}
}

2
boa_icu_provider/Cargo.toml

@ -17,7 +17,7 @@ icu_provider = { version = "1.1.0", features = ["serde", "sync"] }
icu_provider_blob = "1.1.0" icu_provider_blob = "1.1.0"
icu_provider_adapters = { version = "1.1.0", features = ["serde"] } icu_provider_adapters = { version = "1.1.0", features = ["serde"] }
once_cell = {version = "1.17.1", default-features = false, features = ["critical-section"]} once_cell = {version = "1.17.1", default-features = false, features = ["critical-section"]}
icu_datagen = { version = "1.1.1", optional = true } icu_datagen = { version = "1.1.2", optional = true }
log = { version = "0.4.17", optional = true } log = { version = "0.4.17", optional = true }
simple_logger = { version = "4.1.0", optional = true } simple_logger = { version = "4.1.0", optional = true }

1
boa_macros/Cargo.toml

@ -8,7 +8,6 @@ license.workspace = true
repository.workspace = true repository.workspace = true
rust-version.workspace = true rust-version.workspace = true
[lib] [lib]
proc-macro = true proc-macro = true

147
boa_macros/src/lib.rs

@ -58,13 +58,12 @@
)] )]
use proc_macro::TokenStream; use proc_macro::TokenStream;
use proc_macro2::Ident;
use quote::{quote, ToTokens}; use quote::{quote, ToTokens};
use syn::{ use syn::{
parse::{Parse, ParseStream}, parse::{Parse, ParseStream},
parse_macro_input, parse_macro_input,
punctuated::Punctuated, punctuated::Punctuated,
Expr, ExprLit, Lit, LitStr, Token, Data, DeriveInput, Expr, ExprLit, Fields, FieldsNamed, Ident, Lit, LitStr, Token,
}; };
use synstructure::{decl_derive, AddBounds, Structure}; use synstructure::{decl_derive, AddBounds, Structure};
@ -182,10 +181,14 @@ pub fn static_syms(input: TokenStream) -> TokenStream {
/// ///
/// `COMMON_STRINGS_UTF8`, `COMMON_STRINGS_UTF16` and the constants /// `COMMON_STRINGS_UTF8`, `COMMON_STRINGS_UTF16` and the constants
/// defined in [`Sym`] must always be in sync. /// defined in [`Sym`] must always be in sync.
// FIXME: use phf when const expressions are allowed. https://github.com/rust-phf/rust-phf/issues/188 // FIXME: use phf when const expressions are allowed.
// <https://github.com/rust-phf/rust-phf/issues/188>
pub(super) static COMMON_STRINGS_UTF16: ::once_cell::sync::Lazy<Set<&'static [u16]>> = pub(super) static COMMON_STRINGS_UTF16: ::once_cell::sync::Lazy<Set<&'static [u16]>> =
::once_cell::sync::Lazy::new(|| { ::once_cell::sync::Lazy::new(|| {
let mut set = Set::with_capacity_and_hasher(COMMON_STRINGS_UTF8.len(), ::core::hash::BuildHasherDefault::default()); let mut set = Set::with_capacity_and_hasher(
COMMON_STRINGS_UTF8.len(),
::core::hash::BuildHasherDefault::default()
);
#( #(
set.insert(::boa_macros::utf16!(#literals)); set.insert(::boa_macros::utf16!(#literals));
)* )*
@ -216,10 +219,11 @@ pub fn utf16(input: TokenStream) -> TokenStream {
decl_derive! { decl_derive! {
[Trace, attributes(unsafe_ignore_trace)] => [Trace, attributes(unsafe_ignore_trace)] =>
/// Derive the Trace trait. /// Derive the `Trace` trait.
derive_trace derive_trace
} }
/// Derives the `Trace` trait.
fn derive_trace(mut s: Structure<'_>) -> proc_macro2::TokenStream { fn derive_trace(mut s: Structure<'_>) -> proc_macro2::TokenStream {
s.filter(|bi| { s.filter(|bi| {
!bi.ast() !bi.ast()
@ -299,11 +303,142 @@ fn derive_trace(mut s: Structure<'_>) -> proc_macro2::TokenStream {
decl_derive! { decl_derive! {
[Finalize] => [Finalize] =>
/// Derive the Finalize trait. /// Derive the `Finalize` trait.
derive_finalize derive_finalize
} }
/// Derives the `Finalize` trait.
#[allow(clippy::needless_pass_by_value)] #[allow(clippy::needless_pass_by_value)]
fn derive_finalize(s: Structure<'_>) -> proc_macro2::TokenStream { fn derive_finalize(s: Structure<'_>) -> proc_macro2::TokenStream {
s.unbound_impl(quote!(::boa_gc::Finalize), quote!()) s.unbound_impl(quote!(::boa_gc::Finalize), quote!())
} }
/// Derives the `TryFromJs` trait, with the `#[boa()]` attribute.
///
/// # Panics
///
/// It will panic if the user tries to derive the `TryFromJs` trait in an `enum` or a tuple struct.
#[proc_macro_derive(TryFromJs, attributes(boa))]
pub fn derive_try_from_js(input: TokenStream) -> TokenStream {
// Parse the input tokens into a syntax tree
let input = parse_macro_input!(input as DeriveInput);
let Data::Struct(data) = input.data else {
panic!("you can only derive TryFromJs for structs");
};
let Fields::Named(fields) = data.fields else {
panic!("you can only derive TryFromJs for named-field structs")
};
let conv = generate_conversion(fields).unwrap_or_else(to_compile_errors);
let type_name = input.ident;
// Build the output, possibly using quasi-quotation
let expanded = quote! {
impl boa_engine::value::TryFromJs for #type_name {
fn try_from_js(value: &boa_engine::JsValue, context: &mut boa_engine::Context)
-> boa_engine::JsResult<Self> {
match value {
boa_engine::JsValue::Object(o) => {#conv},
_ => Err(boa_engine::JsError::from(
boa_engine::JsNativeError::typ()
.with_message("cannot convert value to a #type_name")
)),
}
}
}
};
// Hand the output tokens back to the compiler
expanded.into()
}
/// Generates the conversion field by field.
fn generate_conversion(fields: FieldsNamed) -> Result<proc_macro2::TokenStream, Vec<syn::Error>> {
use syn::spanned::Spanned;
let mut field_list = Vec::with_capacity(fields.named.len());
let mut final_fields = Vec::with_capacity(fields.named.len());
for field in fields.named {
let span = field.span();
let name = field.ident.ok_or_else(|| {
vec![syn::Error::new(
span,
"you can only derive `TryFromJs` for named-field structs",
)]
})?;
let name_str = format!("{name}");
field_list.push(name.clone());
let error_str = format!("cannot get property {name_str} of value");
let mut from_js_with = None;
if let Some(attr) = field
.attrs
.into_iter()
.find(|attr| attr.path().is_ident("boa"))
{
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("from_js_with") {
let value = meta.value()?;
from_js_with = Some(value.parse::<LitStr>()?);
Ok(())
} else {
Err(meta.error(
"invalid syntax in the `#[boa()]` attribute. \
Note that this attribute only accepts the following syntax: \
`#[boa(from_js_with = \"fully::qualified::path\")]`",
))
}
})
.map_err(|err| vec![err])?;
}
if let Some(method) = from_js_with {
let ident = Ident::new(&method.value(), method.span());
final_fields.push(quote! {
let #name = #ident(props.get(&#name_str.into()).ok_or_else(|| {
boa_engine::JsError::from(
boa_engine::JsNativeError::typ().with_message(#error_str)
)
})?.value().ok_or_else(|| {
boa_engine::JsError::from(
boa_engine::JsNativeError::typ().with_message(#error_str)
)
})?, context)?;
});
} else {
final_fields.push(quote! {
let #name = props.get(&#name_str.into()).ok_or_else(|| {
boa_engine::JsError::from(
boa_engine::JsNativeError::typ().with_message(#error_str)
)
})?.value().ok_or_else(|| {
boa_engine::JsError::from(
boa_engine::JsNativeError::typ().with_message(#error_str)
)
})?.clone().try_js_into(context)?;
});
}
}
Ok(quote! {
let o = o.borrow();
let props = o.properties();
#(#final_fields)*
Ok(Self {
#(#field_list),*
})
})
}
/// Generates a list of compile errors.
#[allow(clippy::needless_pass_by_value)]
fn to_compile_errors(errors: Vec<syn::Error>) -> proc_macro2::TokenStream {
let compile_errors = errors.iter().map(syn::Error::to_compile_error);
quote!(#(#compile_errors)*)
}

0
boa_macros/tests/utf16.rs → boa_macros/tests/tests.rs

16
boa_macros_tests/Cargo.toml

@ -0,0 +1,16 @@
[package]
name = "boa_macros_tests"
description = "Testing crate for boa_macros"
keywords = ["javascript", "ECMASCript", "compiler", "tester"]
publish = false
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true
[dev-dependencies]
trybuild = "1.0.80"
boa_macros.workspace = true
boa_engine.workspace = true

20
boa_macros_tests/tests/derive/from_js_with.rs

@ -0,0 +1,20 @@
use boa_engine::{value::TryFromJs, Context, JsNativeError, JsResult, JsValue};
#[derive(TryFromJs)]
struct TestStruct {
inner: bool,
#[boa(from_js_with = "lossy_float")]
my_int: i16,
}
fn main() {}
fn lossy_float(value: &JsValue, _context: &mut Context) -> JsResult<i16> {
match value {
JsValue::Rational(r) => Ok(r.round() as i16),
JsValue::Integer(i) => Ok(*i as i16),
_ => Err(JsNativeError::typ()
.with_message("cannot convert value to an i16")
.into()),
}
}

8
boa_macros_tests/tests/derive/simple_struct.rs

@ -0,0 +1,8 @@
use boa_engine::value::TryFromJs;
#[derive(TryFromJs)]
struct TestStruct {
inner: bool,
}
fn main() {}

6
boa_macros_tests/tests/tests.rs

@ -0,0 +1,6 @@
#[test]
fn try_from_js() {
let t = trybuild::TestCases::new();
t.pass("tests/derive/simple_struct.rs");
t.pass("tests/derive/from_js_with.rs");
}
Loading…
Cancel
Save