From 46ebd948b1d00f98fb7a3f90420ebde170ae077f Mon Sep 17 00:00:00 2001 From: jerrykingxyz Date: Thu, 10 Oct 2024 14:22:32 +0800 Subject: [PATCH 1/3] feat: rspack cacheable --- Cargo.lock | 342 ++++++++++++++---- Cargo.toml | 5 +- crates/rspack_cacheable/Cargo.toml | 22 ++ crates/rspack_cacheable/src/context.rs | 65 ++++ crates/rspack_cacheable/src/deserialize.rs | 86 +++++ crates/rspack_cacheable/src/dyn/mod.rs | 174 +++++++++ crates/rspack_cacheable/src/dyn/validation.rs | 49 +++ crates/rspack_cacheable/src/lib.rs | 19 + crates/rspack_cacheable/src/serialize.rs | 83 +++++ crates/rspack_cacheable/src/utils/mod.rs | 3 + .../src/utils/type_wrapper.rs | 108 ++++++ crates/rspack_cacheable/src/with/as.rs | 71 ++++ .../rspack_cacheable/src/with/as_cacheable.rs | 41 +++ crates/rspack_cacheable/src/with/as_inner.rs | 159 ++++++++ crates/rspack_cacheable/src/with/as_map.rs | 222 ++++++++++++ .../src/with/as_preset/camino.rs | 41 +++ .../src/with/as_preset/json.rs | 52 +++ .../src/with/as_preset/lightningcss.rs | 56 +++ .../src/with/as_preset/mod.rs | 10 + .../src/with/as_preset/rspack_resolver.rs | 107 ++++++ .../src/with/as_preset/rspack_source/mod.rs | 103 ++++++ .../src/with/as_preset/serde_json.rs | 51 +++ .../src/with/as_preset/swc.rs | 41 +++ .../src/with/as_preset/ustr.rs | 41 +++ .../rspack_cacheable/src/with/as_ref_str.rs | 89 +++++ crates/rspack_cacheable/src/with/as_string.rs | 88 +++++ crates/rspack_cacheable/src/with/as_tuple2.rs | 63 ++++ crates/rspack_cacheable/src/with/as_tuple3.rs | 72 ++++ crates/rspack_cacheable/src/with/as_vec.rs | 144 ++++++++ crates/rspack_cacheable/src/with/mod.rs | 25 ++ .../rspack_cacheable/src/with/unsupported.rs | 34 ++ crates/rspack_cacheable_test/Cargo.toml | 21 ++ crates/rspack_cacheable_test/LICENSE | 22 ++ crates/rspack_cacheable_test/tests/context.rs | 60 +++ .../tests/macro/cacheable.rs | 158 ++++++++ .../tests/macro/cacheable_dyn.rs | 139 +++++++ .../tests/macro/manual_cacheable.rs | 258 +++++++++++++ .../tests/macro/manual_cacheable_dyn.rs | 280 ++++++++++++++ .../manual_cacheable_dyn_with_generics.rs | 271 ++++++++++++++ .../rspack_cacheable_test/tests/macro/mod.rs | 5 + .../rspack_cacheable_test/tests/macro_case.rs | 1 + crates/rspack_cacheable_test/tests/with/as.rs | 40 ++ .../tests/with/as_cacheable.rs | 25 ++ .../tests/with/as_inner.rs | 30 ++ .../tests/with/as_map.rs | 60 +++ .../tests/with/as_preset/camino.rs | 20 + .../tests/with/as_preset/json.rs | 20 + .../tests/with/as_preset/lightningcss.rs | 22 ++ .../tests/with/as_preset/mod.rs | 7 + .../tests/with/as_preset/rspack_resolver.rs | 37 ++ .../tests/with/as_preset/serde_json.rs | 20 + .../tests/with/as_preset/swc.rs | 20 + .../tests/with/as_preset/ustr.rs | 20 + .../tests/with/as_ref_str.rs | 44 +++ .../tests/with/as_string.rs | 53 +++ .../tests/with/as_tuple2.rs | 29 ++ .../tests/with/as_tuple3.rs | 29 ++ .../tests/with/as_vec.rs | 46 +++ .../rspack_cacheable_test/tests/with/mod.rs | 11 + .../tests/with/unsupported.rs | 19 + .../rspack_cacheable_test/tests/with_case.rs | 1 + crates/rspack_macros/src/cacheable.rs | 241 ++++++++++++ crates/rspack_macros/src/cacheable_dyn.rs | 234 ++++++++++++ crates/rspack_macros/src/lib.rs | 29 ++ 64 files changed, 4662 insertions(+), 76 deletions(-) create mode 100644 crates/rspack_cacheable/Cargo.toml create mode 100644 crates/rspack_cacheable/src/context.rs create mode 100644 crates/rspack_cacheable/src/deserialize.rs create mode 100644 crates/rspack_cacheable/src/dyn/mod.rs create mode 100644 crates/rspack_cacheable/src/dyn/validation.rs create mode 100644 crates/rspack_cacheable/src/lib.rs create mode 100644 crates/rspack_cacheable/src/serialize.rs create mode 100644 crates/rspack_cacheable/src/utils/mod.rs create mode 100644 crates/rspack_cacheable/src/utils/type_wrapper.rs create mode 100644 crates/rspack_cacheable/src/with/as.rs create mode 100644 crates/rspack_cacheable/src/with/as_cacheable.rs create mode 100644 crates/rspack_cacheable/src/with/as_inner.rs create mode 100644 crates/rspack_cacheable/src/with/as_map.rs create mode 100644 crates/rspack_cacheable/src/with/as_preset/camino.rs create mode 100644 crates/rspack_cacheable/src/with/as_preset/json.rs create mode 100644 crates/rspack_cacheable/src/with/as_preset/lightningcss.rs create mode 100644 crates/rspack_cacheable/src/with/as_preset/mod.rs create mode 100644 crates/rspack_cacheable/src/with/as_preset/rspack_resolver.rs create mode 100644 crates/rspack_cacheable/src/with/as_preset/rspack_source/mod.rs create mode 100644 crates/rspack_cacheable/src/with/as_preset/serde_json.rs create mode 100644 crates/rspack_cacheable/src/with/as_preset/swc.rs create mode 100644 crates/rspack_cacheable/src/with/as_preset/ustr.rs create mode 100644 crates/rspack_cacheable/src/with/as_ref_str.rs create mode 100644 crates/rspack_cacheable/src/with/as_string.rs create mode 100644 crates/rspack_cacheable/src/with/as_tuple2.rs create mode 100644 crates/rspack_cacheable/src/with/as_tuple3.rs create mode 100644 crates/rspack_cacheable/src/with/as_vec.rs create mode 100644 crates/rspack_cacheable/src/with/mod.rs create mode 100644 crates/rspack_cacheable/src/with/unsupported.rs create mode 100644 crates/rspack_cacheable_test/Cargo.toml create mode 100644 crates/rspack_cacheable_test/LICENSE create mode 100644 crates/rspack_cacheable_test/tests/context.rs create mode 100644 crates/rspack_cacheable_test/tests/macro/cacheable.rs create mode 100644 crates/rspack_cacheable_test/tests/macro/cacheable_dyn.rs create mode 100644 crates/rspack_cacheable_test/tests/macro/manual_cacheable.rs create mode 100644 crates/rspack_cacheable_test/tests/macro/manual_cacheable_dyn.rs create mode 100644 crates/rspack_cacheable_test/tests/macro/manual_cacheable_dyn_with_generics.rs create mode 100644 crates/rspack_cacheable_test/tests/macro/mod.rs create mode 100644 crates/rspack_cacheable_test/tests/macro_case.rs create mode 100644 crates/rspack_cacheable_test/tests/with/as.rs create mode 100644 crates/rspack_cacheable_test/tests/with/as_cacheable.rs create mode 100644 crates/rspack_cacheable_test/tests/with/as_inner.rs create mode 100644 crates/rspack_cacheable_test/tests/with/as_map.rs create mode 100644 crates/rspack_cacheable_test/tests/with/as_preset/camino.rs create mode 100644 crates/rspack_cacheable_test/tests/with/as_preset/json.rs create mode 100644 crates/rspack_cacheable_test/tests/with/as_preset/lightningcss.rs create mode 100644 crates/rspack_cacheable_test/tests/with/as_preset/mod.rs create mode 100644 crates/rspack_cacheable_test/tests/with/as_preset/rspack_resolver.rs create mode 100644 crates/rspack_cacheable_test/tests/with/as_preset/serde_json.rs create mode 100644 crates/rspack_cacheable_test/tests/with/as_preset/swc.rs create mode 100644 crates/rspack_cacheable_test/tests/with/as_preset/ustr.rs create mode 100644 crates/rspack_cacheable_test/tests/with/as_ref_str.rs create mode 100644 crates/rspack_cacheable_test/tests/with/as_string.rs create mode 100644 crates/rspack_cacheable_test/tests/with/as_tuple2.rs create mode 100644 crates/rspack_cacheable_test/tests/with/as_tuple3.rs create mode 100644 crates/rspack_cacheable_test/tests/with/as_vec.rs create mode 100644 crates/rspack_cacheable_test/tests/with/mod.rs create mode 100644 crates/rspack_cacheable_test/tests/with/unsupported.rs create mode 100644 crates/rspack_cacheable_test/tests/with_case.rs create mode 100644 crates/rspack_macros/src/cacheable.rs create mode 100644 crates/rspack_macros/src/cacheable_dyn.rs diff --git a/Cargo.lock b/Cargo.lock index d86bba6a2aa..48affa75e66 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -124,7 +124,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -135,7 +135,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -168,7 +168,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -179,7 +179,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -205,7 +205,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -423,8 +423,20 @@ version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6372023ac861f6e6dc89c8344a8f398fb42aaba2b5dbc649ca0c0e9dbcb627" dependencies = [ - "bytecheck_derive", - "ptr_meta", + "bytecheck_derive 0.6.11", + "ptr_meta 0.1.4", + "simdutf8", +] + +[[package]] +name = "bytecheck" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50c8f430744b23b54ad15161fcbc22d82a29b73eacbe425fea23ec822600bc6f" +dependencies = [ + "bytecheck_derive 0.8.0", + "ptr_meta 0.3.0", + "rancor", "simdutf8", ] @@ -439,6 +451,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "bytecheck_derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523363cbe1df49b68215efdf500b103ac3b0fb4836aed6d15689a076eadb8fff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + [[package]] name = "byteorder" version = "1.5.0" @@ -889,7 +912,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.63", + "syn 2.0.79", +] + +[[package]] +name = "ctor" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" +dependencies = [ + "quote", + "syn 1.0.109", ] [[package]] @@ -899,7 +932,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eed5fff0d93c7559121e9c72bf9c242295869396255071ff2cb1617147b608c5" dependencies = [ "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -947,7 +980,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -969,7 +1002,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core 0.20.10", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -1084,7 +1117,7 @@ dependencies = [ "darling 0.20.10", "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -1104,7 +1137,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4abae7035bf79b9877b779505d8cf3749285b80c43941eda66604841889451dc" dependencies = [ "derive_builder_core 0.20.1", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -1215,7 +1248,7 @@ dependencies = [ "darling 0.20.10", "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -1332,7 +1365,7 @@ checksum = "8d7ccf961415e7aa17ef93dcb6c2441faaa8e768abe09e659b908089546f74c5" dependencies = [ "proc-macro2", "swc_macros_common", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -1432,7 +1465,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -1510,6 +1543,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "ghost" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0e085ded9f1267c32176b40921b9754c474f7dd96f7e808d4a982e48aa1e854" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + [[package]] name = "gimli" version = "0.26.2" @@ -1945,6 +1989,28 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "inventory" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0eb5160c60ba1e809707918ee329adb99d222888155835c6feedba19f6c3fd4" +dependencies = [ + "ctor 0.1.26", + "ghost", + "inventory-impl", +] + +[[package]] +name = "inventory-impl" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e41b53715c6f0c4be49510bb82dee2c1e51c8586d885abe65396e82ed518548" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "is-macro" version = "0.3.5" @@ -1954,7 +2020,7 @@ dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -2320,7 +2386,7 @@ checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -2331,7 +2397,7 @@ checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -2392,6 +2458,26 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" +[[package]] +name = "munge" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64142d38c84badf60abf06ff9bd80ad2174306a5b11bd4706535090a30a419df" +dependencies = [ + "munge_macro", +] + +[[package]] +name = "munge_macro" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bb5c1d8184f13f7d0ccbeeca0def2f9a181bce2624302793005f5ca8aa62e5e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + [[package]] name = "napi-build" version = "2.1.2" @@ -2409,7 +2495,7 @@ dependencies = [ "napi-derive-backend", "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -2424,7 +2510,7 @@ dependencies = [ "quote", "regex", "semver 1.0.20", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -2435,7 +2521,7 @@ checksum = "8122073a4a01e851f0a9bbc4322b8f8cdf6f9faafc260f005cfd782098aff4fb" dependencies = [ "anyhow", "bitflags 2.5.0", - "ctor", + "ctor 0.2.3", "napi-derive", "napi-sys", "once_cell", @@ -2648,7 +2734,7 @@ checksum = "485b74d7218068b2b7c0e3ff12fbc61ae11d57cb5d8224f525bd304c6be05bbb" dependencies = [ "base64-simd 0.7.0", "data-url", - "rkyv", + "rkyv 0.7.45", "serde", "serde_json", "vlq", @@ -2746,7 +2832,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -2829,7 +2915,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -2867,7 +2953,7 @@ checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -2890,7 +2976,7 @@ checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -2981,9 +3067,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.82" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" +checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" dependencies = [ "unicode-ident", ] @@ -3008,7 +3094,7 @@ dependencies = [ "itertools 0.13.0", "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -3035,7 +3121,16 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" dependencies = [ - "ptr_meta_derive", + "ptr_meta_derive 0.1.4", +] + +[[package]] +name = "ptr_meta" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9e76f66d3f9606f44e45598d155cb13ecf09f4a28199e48daf8c8fc937ea90" +dependencies = [ + "ptr_meta_derive 0.3.0", ] [[package]] @@ -3049,6 +3144,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ptr_meta_derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca414edb151b4c8d125c12566ab0d74dc9cdba36fb80eb7b848c15f495fd32d1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + [[package]] name = "pulldown-cmark" version = "0.8.0" @@ -3081,6 +3187,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce082a9940a7ace2ad4a8b7d0b1eac6aa378895f18be598230c5f2284ac05426" +[[package]] +name = "rancor" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf5f7161924b9d1cea0e4cabc97c372cea92b5f927fc13c6bca67157a0ad947" +dependencies = [ + "ptr_meta 0.3.0", +] + [[package]] name = "rand" version = "0.8.5" @@ -3166,7 +3281,7 @@ checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -3253,7 +3368,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581008d2099240d37fb08d77ad713bcaec2c4d89d50b5b21a8bb1996bbab68ab" dependencies = [ - "bytecheck", + "bytecheck 0.6.11", +] + +[[package]] +name = "rend" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a35e8a6bf28cd121053a66aa2e6a2e3eaffad4a60012179f0e864aa5ffeff215" +dependencies = [ + "bytecheck 0.8.0", ] [[package]] @@ -3269,18 +3393,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" dependencies = [ "bitvec", - "bytecheck", + "bytecheck 0.6.11", "bytes", "hashbrown 0.12.3", "indexmap 1.9.3", - "ptr_meta", - "rend", - "rkyv_derive", + "ptr_meta 0.1.4", + "rend 0.4.0", + "rkyv_derive 0.7.45", "seahash", "tinyvec", "uuid", ] +[[package]] +name = "rkyv" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395027076c569819ea6035ee62e664f5e03d74e281744f55261dd1afd939212b" +dependencies = [ + "bytecheck 0.8.0", + "bytes", + "hashbrown 0.14.5", + "indexmap 2.2.6", + "munge", + "ptr_meta 0.3.0", + "rancor", + "rend 0.5.2", + "rkyv_derive 0.8.8", + "tinyvec", + "uuid", +] + [[package]] name = "rkyv_derive" version = "0.7.45" @@ -3292,6 +3435,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "rkyv_derive" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cb82b74b4810f07e460852c32f522e979787691b0b7b7439fe473e49d49b2f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + [[package]] name = "ropey" version = "1.6.1" @@ -3420,6 +3574,44 @@ dependencies = [ "serde_json", ] +[[package]] +name = "rspack_cacheable" +version = "0.1.0" +dependencies = [ + "camino", + "dashmap 5.5.3", + "hashlink", + "indexmap 2.2.6", + "inventory", + "json", + "lightningcss", + "once_cell", + "rkyv 0.8.8", + "rspack_macros", + "rspack_resolver", + "rspack_sources", + "serde_json", + "swc_core", + "ustr-fxhash", +] + +[[package]] +name = "rspack_cacheable_test" +version = "0.1.0" +dependencies = [ + "camino", + "dashmap 5.5.3", + "json", + "lightningcss", + "once_cell", + "rspack_cacheable", + "rspack_resolver", + "rustc-hash 1.1.0", + "serde_json", + "swc_core", + "ustr-fxhash", +] + [[package]] name = "rspack_collections" version = "0.1.0" @@ -3679,7 +3871,7 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -3714,7 +3906,7 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -4557,7 +4749,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -4636,7 +4828,7 @@ checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -4647,7 +4839,7 @@ checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -4905,7 +5097,7 @@ dependencies = [ "pmutil", "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -4950,7 +5142,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -5060,7 +5252,7 @@ checksum = "52cacc28f0ada8e4e31a720dd849ff06864b10e6ab0a1aaa99c06456cfe046af" dependencies = [ "bumpalo", "hashbrown 0.14.5", - "ptr_meta", + "ptr_meta 0.1.4", "rustc-hash 1.1.0", "triomphe", ] @@ -5071,10 +5263,10 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d7211e5c57ea972f32b8a104d7006c4a68d094ec30c6a73bcd20d4d6c473c7c" dependencies = [ - "bytecheck", + "bytecheck 0.6.11", "hstr", "once_cell", - "rkyv", + "rkyv 0.7.45", "rustc-hash 1.1.0", "serde", ] @@ -5103,7 +5295,7 @@ dependencies = [ "anyhow", "ast_node", "better_scoped_tls", - "bytecheck", + "bytecheck 0.6.11", "cfg-if", "either", "from_variant", @@ -5111,7 +5303,7 @@ dependencies = [ "num-bigint", "once_cell", "parking_lot", - "rkyv", + "rkyv 0.7.45", "rustc-hash 1.1.0", "serde", "siphasher", @@ -5175,7 +5367,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -5213,11 +5405,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6795be2785b968ccff06096bc758b306459f05fc936e6363b4dd39fb27fba22a" dependencies = [ "bitflags 2.5.0", - "bytecheck", + "bytecheck 0.6.11", "is-macro", "num-bigint", "phf 0.11.2", - "rkyv", + "rkyv 0.7.45", "scoped-tls", "serde", "string_enum", @@ -5255,7 +5447,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -5606,7 +5798,7 @@ dependencies = [ "swc_ecma_ast", "swc_ecma_parser", "swc_macros_common", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -5712,7 +5904,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -5889,7 +6081,7 @@ checksum = "e96e15288bf385ab85eb83cff7f9e2d834348da58d0a31b33bdb572e66ee413e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -5966,7 +6158,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -6040,7 +6232,7 @@ checksum = "a509f56fca05b39ba6c15f3e58636c3924c78347d63853632ed2ffcb6f5a0ac7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -6073,7 +6265,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2b12f6e1064370116757b9aebc33ab82c123eabc635d00b38770a1f2dbebdc8" dependencies = [ "better_scoped_tls", - "rkyv", + "rkyv 0.7.45", "swc_common", "swc_ecma_ast", "swc_trace_macro", @@ -6123,7 +6315,7 @@ checksum = "4c78717a841565df57f811376a3d19c9156091c55175e12d378f3a522de70cef" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -6174,9 +6366,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.63" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -6308,7 +6500,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -6396,7 +6588,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -6565,7 +6757,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] [[package]] @@ -6902,12 +7094,12 @@ dependencies = [ "async-trait", "base64 0.21.7", "bincode", - "bytecheck", + "bytecheck 0.6.11", "bytes", "derivative", "futures-util", "pin-project-lite", - "rkyv", + "rkyv 0.7.45", "serde", "smoltcp", "thiserror", @@ -7146,7 +7338,7 @@ dependencies = [ "memmap2 0.5.10", "more-asserts", "region", - "rkyv", + "rkyv 0.7.45", "self_cell", "shared-buffer", "smallvec", @@ -7221,12 +7413,12 @@ dependencies = [ "async-trait", "base64 0.21.7", "bincode", - "bytecheck", + "bytecheck 0.6.11", "bytes", "derivative", "lz4_flex", "num_enum", - "rkyv", + "rkyv 0.7.45", "serde", "serde_json", "thiserror", @@ -7243,14 +7435,14 @@ version = "4.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d22a00f1a90e9e66d5427853f41e76d8ab89e03eb3034debd11933607fef56a" dependencies = [ - "bytecheck", + "bytecheck 0.6.11", "enum-iterator", "enumset", "getrandom", "hex", "indexmap 1.9.3", "more-asserts", - "rkyv", + "rkyv 0.7.45", "serde", "sha2", "target-lexicon", @@ -7298,7 +7490,7 @@ dependencies = [ "base64 0.21.7", "bincode", "blake3", - "bytecheck", + "bytecheck 0.6.11", "bytes", "cfg-if", "cooked-waker", @@ -7319,7 +7511,7 @@ dependencies = [ "pin-project", "pin-utils", "rand", - "rkyv", + "rkyv 0.7.45", "rusty_pool", "semver 1.0.20", "serde", @@ -7758,5 +7950,5 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.79", ] diff --git a/Cargo.toml b/Cargo.toml index 8d31552369c..ad2b39d8151 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,8 +73,11 @@ napi = { package = "napi-h", version = "=2.16.1" } napi-build = { version = "2" } napi-derive = { version = "2" } +# Serialize and Deserialize +inventory = { version = "0.1" } +rkyv = { version = "=0.8.8" } + # Must be pinned with the same swc versions -#rkyv = { version = "=0.7.44" } # synced with swc wasm plugin swc_config = { version = "=1.0.0" } swc_core = { version = "=1.0.0", default-features = false } swc_ecma_minifier = { version = "=1.0.0", default-features = false } diff --git a/crates/rspack_cacheable/Cargo.toml b/crates/rspack_cacheable/Cargo.toml new file mode 100644 index 00000000000..c1550a267b5 --- /dev/null +++ b/crates/rspack_cacheable/Cargo.toml @@ -0,0 +1,22 @@ +[package] +edition = "2021" +license = "MIT" +name = "rspack_cacheable" +version = "0.1.0" + +[dependencies] +camino = { workspace = true } +dashmap = { workspace = true } +hashlink = { workspace = true } +indexmap = { workspace = true } +inventory = { workspace = true } +json = { workspace = true } +lightningcss = { workspace = true } +once_cell = { workspace = true } +rkyv = { workspace = true } +rspack_macros = { path = "../rspack_macros" } +rspack_resolver = { workspace = true } +rspack_sources = { workspace = true } +serde_json = { workspace = true } +swc_core = { workspace = true, features = ["ecma_ast"] } +ustr = { workspace = true } diff --git a/crates/rspack_cacheable/src/context.rs b/crates/rspack_cacheable/src/context.rs new file mode 100644 index 00000000000..6a37e24e36b --- /dev/null +++ b/crates/rspack_cacheable/src/context.rs @@ -0,0 +1,65 @@ +use std::{any::Any, ptr::NonNull}; + +use rkyv::{ + de::{ErasedPtr, Pooling, PoolingState}, + ser::{sharing::SharingState, Sharing}, +}; + +use crate::{DeserializeError, SerializeError}; + +const CONTEXT_ADDR: usize = 0; +unsafe fn default_drop(_: ErasedPtr) {} + +/// A context wrapper that provides shared context methods +pub struct ContextGuard<'a> { + context: &'a dyn Any, +} + +impl<'a> ContextGuard<'a> { + pub fn new(context: &'a dyn Any) -> Self { + Self { context } + } + + pub fn add_to_sharing>( + &self, + sharing: &mut S, + ) -> Result<(), SerializeError> { + sharing.start_sharing(CONTEXT_ADDR); + sharing.finish_sharing(CONTEXT_ADDR, self as *const _ as usize) + } + + pub fn sharing_context>( + sharing: &'a mut S, + ) -> Result<&'a dyn Any, SerializeError> { + match sharing.start_sharing(CONTEXT_ADDR) { + SharingState::Finished(addr) => { + let guard: &Self = unsafe { &*(addr as *const Self) }; + Ok(guard.context) + } + _ => Err(SerializeError::NoContext), + } + } + + pub fn add_to_pooling>( + &self, + pooling: &mut P, + ) -> Result<(), DeserializeError> { + unsafe { + let ctx_ptr = ErasedPtr::new(NonNull::new_unchecked(self as *const _ as *mut ())); + pooling.start_pooling(CONTEXT_ADDR); + pooling.finish_pooling(CONTEXT_ADDR, ctx_ptr, default_drop) + } + } + + pub fn pooling_context>( + pooling: &'a mut P, + ) -> Result<&'a dyn Any, DeserializeError> { + match pooling.start_pooling(CONTEXT_ADDR) { + PoolingState::Finished(ptr) => { + let guard: &Self = unsafe { &*(ptr.data_address() as *const Self) }; + Ok(guard.context) + } + _ => Err(DeserializeError::NoContext), + } + } +} diff --git a/crates/rspack_cacheable/src/deserialize.rs b/crates/rspack_cacheable/src/deserialize.rs new file mode 100644 index 00000000000..a5981cea4f4 --- /dev/null +++ b/crates/rspack_cacheable/src/deserialize.rs @@ -0,0 +1,86 @@ +use std::any::Any; + +use rkyv::{ + access, + api::{deserialize_using, high::HighValidator}, + bytecheck::CheckBytes, + de::Pool, + rancor::{BoxedError, Source, Strategy, Trace}, + Archive, Deserialize, +}; + +use crate::context::ContextGuard; + +#[derive(Debug)] +pub enum DeserializeError { + BoxedError(BoxedError), + MessageError(&'static str), + DynCheckBytesNotRegister, + NoContext, + UnsupportedField, +} + +impl std::fmt::Display for DeserializeError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::BoxedError(error) => error.fmt(f), + Self::MessageError(msg) => { + write!(f, "{}", msg) + } + Self::DynCheckBytesNotRegister => { + write!(f, "cacheable_dyn check bytes not register") + } + Self::NoContext => { + write!(f, "no context") + } + Self::UnsupportedField => { + write!(f, "unsupported field") + } + } + } +} + +impl std::error::Error for DeserializeError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::BoxedError(error) => error.source(), + _ => None, + } + } +} + +impl Trace for DeserializeError { + fn trace(self, trace: R) -> Self + where + R: std::fmt::Debug + std::fmt::Display + Send + Sync + 'static, + { + Self::BoxedError(BoxedError::trace(BoxedError::new(self), trace)) + } +} + +impl Source for DeserializeError { + fn new(source: T) -> Self { + Self::BoxedError(BoxedError::new(source)) + } +} + +pub type Validator<'a> = HighValidator<'a, DeserializeError>; +pub type Deserializer = Strategy; + +/// Transform bytes to struct +/// +/// This function implementation refers to rkyv::from_bytes and +/// add custom error and context support +pub fn from_bytes(bytes: &[u8], context: &C) -> Result +where + T: Archive, + T::Archived: for<'a> CheckBytes> + Deserialize, +{ + let guard = ContextGuard::new(context); + let mut deserializer = Pool::default(); + guard.add_to_pooling(&mut deserializer)?; + deserialize_using( + access::(bytes)?, + &mut deserializer, + ) +} diff --git a/crates/rspack_cacheable/src/dyn/mod.rs b/crates/rspack_cacheable/src/dyn/mod.rs new file mode 100644 index 00000000000..1e0f49c8dd4 --- /dev/null +++ b/crates/rspack_cacheable/src/dyn/mod.rs @@ -0,0 +1,174 @@ +use core::marker::PhantomData; +use std::{ + collections::HashMap, + hash::{Hash, Hasher}, +}; + +use inventory; +use rkyv::{ + bytecheck::{CheckBytes, StructCheckContext}, + ptr_meta::{DynMetadata, Pointee}, + rancor::{Fallible, Trace}, + traits::NoUndef, + Archived, Portable, SerializeUnsized, +}; + +pub mod validation; +use crate::{DeserializeError, Deserializer, SerializeError, Serializer}; + +/// A trait object that can be archived. +pub trait SerializeDyn { + /// Writes the value to the serializer and returns the position it was written to. + fn serialize_dyn(&self, serializer: &mut Serializer) -> Result; +} + +impl SerializeDyn for T +where + T: for<'a> SerializeUnsized>, +{ + fn serialize_dyn(&self, serializer: &mut Serializer) -> Result { + self.serialize_unsized(serializer) + } +} + +/// A trait object that can be deserialized. +/// +/// See [`SerializeDyn`] for more information. +pub trait DeserializeDyn { + /// Deserializes this value into the given out pointer. + fn deserialize_dyn( + &self, + deserializer: &mut Deserializer, + out: *mut T, + ) -> Result<(), DeserializeError>; + + /// Returns the pointer metadata for the deserialized form of this type. + fn deserialized_pointer_metadata(&self) -> DynMetadata; +} + +/// The archived version of `DynMetadata`. +pub struct ArchivedDynMetadata { + dyn_id: Archived, + phantom: PhantomData, +} + +impl Default for ArchivedDynMetadata { + fn default() -> Self { + Self { + dyn_id: Archived::::from_native(0), + phantom: PhantomData::default(), + } + } +} +impl Hash for ArchivedDynMetadata { + #[inline] + fn hash(&self, state: &mut H) { + Hash::hash(&self.dyn_id, state); + } +} +impl PartialEq for ArchivedDynMetadata { + #[inline] + fn eq(&self, other: &ArchivedDynMetadata) -> bool { + self.dyn_id == other.dyn_id + } +} +impl Eq for ArchivedDynMetadata {} +impl PartialOrd for ArchivedDynMetadata { + #[inline] + fn partial_cmp(&self, other: &ArchivedDynMetadata) -> Option<::core::cmp::Ordering> { + Some(self.dyn_id.cmp(&other.dyn_id)) + } +} +impl Ord for ArchivedDynMetadata { + #[inline] + fn cmp(&self, other: &ArchivedDynMetadata) -> ::core::cmp::Ordering { + self.dyn_id.cmp(&other.dyn_id) + } +} +impl Clone for ArchivedDynMetadata { + fn clone(&self) -> ArchivedDynMetadata { + *self + } +} +impl Copy for ArchivedDynMetadata {} +impl Unpin for ArchivedDynMetadata {} +unsafe impl Sync for ArchivedDynMetadata {} +unsafe impl Send for ArchivedDynMetadata {} +unsafe impl NoUndef for ArchivedDynMetadata {} +unsafe impl Portable for ArchivedDynMetadata {} +unsafe impl CheckBytes for ArchivedDynMetadata +where + C: Fallible + ?Sized, + C::Error: Trace, + Archived: CheckBytes, + PhantomData: CheckBytes, +{ + unsafe fn check_bytes( + value: *const Self, + context: &mut C, + ) -> ::core::result::Result<(), C::Error> { + Archived::::check_bytes(&raw const (*value).dyn_id, context).map_err(|e| { + C::Error::trace( + e, + StructCheckContext { + struct_name: "ArchivedDynMetadata", + field_name: "dyn_id", + }, + ) + })?; + PhantomData::::check_bytes(&raw const (*value).phantom, context).map_err(|e| { + C::Error::trace( + e, + StructCheckContext { + struct_name: "ArchivedDynMetadata", + field_name: "phantom", + }, + ) + })?; + Ok(()) + } +} + +impl ArchivedDynMetadata { + pub fn new(dyn_id: u64) -> Self { + Self { + dyn_id: Archived::::from_native(dyn_id), + phantom: PhantomData, + } + } + + /// Returns the pointer metadata for the trait object this metadata refers to. + pub fn lookup_metadata(&self) -> DynMetadata { + unsafe { + std::mem::transmute( + *DYN_REGISTRY + .get(&self.dyn_id.to_native()) + .expect("attempted to get vtable for an unregistered impl"), + ) + } + } +} + +pub struct DynEntry { + dyn_id: u64, + vtable: usize, +} + +impl DynEntry { + pub fn new(dyn_id: u64, vtable: usize) -> Self { + Self { dyn_id, vtable } + } +} + +inventory::collect!(DynEntry); + +static DYN_REGISTRY: std::sync::LazyLock> = std::sync::LazyLock::new(|| { + let mut result = HashMap::default(); + for entry in inventory::iter:: { + let old_value = result.insert(entry.dyn_id, entry.vtable); + if old_value.is_some() { + panic!("cacheable_dyn init global REGISTRY error, duplicate implementation.") + } + } + result +}); diff --git a/crates/rspack_cacheable/src/dyn/validation.rs b/crates/rspack_cacheable/src/dyn/validation.rs new file mode 100644 index 00000000000..157b47aeadf --- /dev/null +++ b/crates/rspack_cacheable/src/dyn/validation.rs @@ -0,0 +1,49 @@ +use std::collections::HashMap; + +use rkyv::bytecheck::CheckBytes; + +use crate::{DeserializeError, Validator}; + +type CheckBytesDyn = unsafe fn(*const u8, &mut Validator<'_>) -> Result<(), DeserializeError>; + +/// # Safety +/// +/// Run T::check_bytes +pub unsafe fn default_check_bytes_dyn( + bytes: *const u8, + context: &mut Validator<'_>, +) -> Result<(), DeserializeError> +where + T: for<'a> CheckBytes>, +{ + T::check_bytes(bytes.cast(), context) +} + +pub struct CheckBytesEntry { + vtable: usize, + check_bytes_dyn: CheckBytesDyn, +} + +impl CheckBytesEntry { + #[doc(hidden)] + pub fn new(vtable: usize, check_bytes_dyn: CheckBytesDyn) -> Self { + Self { + vtable, + check_bytes_dyn, + } + } +} + +inventory::collect!(CheckBytesEntry); + +pub static CHECK_BYTES_REGISTRY: std::sync::LazyLock> = + std::sync::LazyLock::new(|| { + let mut result = HashMap::default(); + for entry in inventory::iter:: { + let old_value = result.insert(entry.vtable, entry.check_bytes_dyn); + if old_value.is_some() { + panic!("vtable conflict, a trait implementation was likely added twice (but it's possible there was a hash collision)") + } + } + result + }); diff --git a/crates/rspack_cacheable/src/lib.rs b/crates/rspack_cacheable/src/lib.rs new file mode 100644 index 00000000000..dea6e8e594a --- /dev/null +++ b/crates/rspack_cacheable/src/lib.rs @@ -0,0 +1,19 @@ +pub use rspack_macros::{cacheable, cacheable_dyn}; +pub mod r#dyn; +pub mod utils; +pub mod with; + +mod context; +mod deserialize; +mod serialize; + +#[doc(hidden)] +pub mod __private { + #[doc(hidden)] + pub extern crate inventory; + #[doc(hidden)] + pub extern crate rkyv; +} + +pub use deserialize::{from_bytes, DeserializeError, Deserializer, Validator}; +pub use serialize::{to_bytes, SerializeError, Serializer}; diff --git a/crates/rspack_cacheable/src/serialize.rs b/crates/rspack_cacheable/src/serialize.rs new file mode 100644 index 00000000000..ae4efab1114 --- /dev/null +++ b/crates/rspack_cacheable/src/serialize.rs @@ -0,0 +1,83 @@ +use std::any::Any; + +use rkyv::{ + api::{high::HighSerializer, serialize_using}, + rancor::{BoxedError, Source, Trace}, + ser::{ + allocator::{Arena, ArenaHandle}, + sharing::Share, + Serializer as RkyvSerializer, + }, + util::AlignedVec, + Serialize, +}; + +use crate::context::ContextGuard; + +#[derive(Debug)] +pub enum SerializeError { + BoxedError(BoxedError), + MessageError(&'static str), + NoContext, + UnsupportedField, +} + +impl std::fmt::Display for SerializeError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::BoxedError(error) => error.fmt(f), + Self::MessageError(msg) => { + write!(f, "{}", msg) + } + Self::NoContext => { + write!(f, "no context") + } + Self::UnsupportedField => { + write!(f, "unsupported field") + } + } + } +} + +impl std::error::Error for SerializeError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::BoxedError(error) => error.source(), + _ => None, + } + } +} + +impl Trace for SerializeError { + fn trace(self, trace: R) -> Self + where + R: std::fmt::Debug + std::fmt::Display + Send + Sync + 'static, + { + Self::BoxedError(BoxedError::trace(BoxedError::new(self), trace)) + } +} + +impl Source for SerializeError { + fn new(source: T) -> Self { + Self::BoxedError(BoxedError::new(source)) + } +} + +pub type Serializer<'a> = HighSerializer, SerializeError>; + +/// Transform struct to bytes +/// +/// This function implementation refers to rkyv::to_bytes and +/// add custom error and context support +pub fn to_bytes(value: &T, ctx: &C) -> Result, SerializeError> +where + T: for<'a> Serialize>, +{ + let guard = ContextGuard::new(ctx); + let mut arena = Arena::new(); + let mut serializer = RkyvSerializer::new(AlignedVec::new(), arena.acquire(), Share::new()); + guard.add_to_sharing(&mut serializer)?; + + serialize_using(value, &mut serializer)?; + Ok(serializer.into_writer().into_vec()) +} diff --git a/crates/rspack_cacheable/src/utils/mod.rs b/crates/rspack_cacheable/src/utils/mod.rs new file mode 100644 index 00000000000..9e149f6f4cd --- /dev/null +++ b/crates/rspack_cacheable/src/utils/mod.rs @@ -0,0 +1,3 @@ +mod type_wrapper; + +pub use type_wrapper::{TypeWrapper, TypeWrapperRef}; diff --git a/crates/rspack_cacheable/src/utils/type_wrapper.rs b/crates/rspack_cacheable/src/utils/type_wrapper.rs new file mode 100644 index 00000000000..150e9599ad5 --- /dev/null +++ b/crates/rspack_cacheable/src/utils/type_wrapper.rs @@ -0,0 +1,108 @@ +use rkyv::{ + bytecheck::{CheckBytes, StructCheckContext}, + rancor::{Fallible, Source, Trace}, + ser::{Allocator, Writer}, + string::{ArchivedString, StringResolver}, + vec::{ArchivedVec, VecResolver}, + Archive, Deserialize, Place, Portable, Serialize, +}; + +pub struct TypeWrapperRef<'a> { + pub type_name: &'a str, + pub bytes: &'a [u8], +} + +pub struct ArchivedTypeWrapper { + type_name: ArchivedString, + bytes: ArchivedVec, +} + +unsafe impl Portable for ArchivedTypeWrapper {} + +pub struct TypeWrapper { + pub type_name: String, + pub bytes: Vec, +} + +impl<'a> Archive for TypeWrapperRef<'a> { + type Archived = ArchivedTypeWrapper; + type Resolver = (StringResolver, VecResolver); + + #[inline] + fn resolve(&self, resolver: Self::Resolver, out: Place) { + let field_ptr = unsafe { &raw mut (*out.ptr()).type_name }; + let field_out = unsafe { Place::from_field_unchecked(out, field_ptr) }; + ArchivedString::resolve_from_str(self.type_name, resolver.0, field_out); + let field_ptr = unsafe { &raw mut (*out.ptr()).bytes }; + let field_out = unsafe { Place::from_field_unchecked(out, field_ptr) }; + ArchivedVec::resolve_from_len(self.bytes.len(), resolver.1, field_out); + } +} + +impl Archive for TypeWrapper { + type Archived = ArchivedTypeWrapper; + type Resolver = (StringResolver, VecResolver); + + #[inline] + fn resolve(&self, _resolver: Self::Resolver, _out: Place) { + unreachable!() + } +} + +impl<'a, S> Serialize for TypeWrapperRef<'a> +where + S: ?Sized + Fallible + Writer + Allocator, + S::Error: Source, +{ + #[inline] + fn serialize(&self, serializer: &mut S) -> Result { + Ok(( + ArchivedString::serialize_from_str(self.type_name, serializer)?, + ArchivedVec::serialize_from_slice(self.bytes, serializer)?, + )) + } +} + +unsafe impl CheckBytes for ArchivedTypeWrapper +where + ArchivedString: CheckBytes, + ArchivedVec: CheckBytes, + C: Fallible + ?Sized, + C::Error: Trace, +{ + unsafe fn check_bytes(bytes: *const Self, context: &mut C) -> Result<(), C::Error> { + ArchivedString::check_bytes(core::ptr::addr_of!((*bytes).type_name), context).map_err(|e| { + ::trace( + e, + StructCheckContext { + struct_name: "ArchivedTypeWrapper", + field_name: "type_name", + }, + ) + })?; + ArchivedVec::::check_bytes(core::ptr::addr_of!((*bytes).bytes), context).map_err(|e| { + ::trace( + e, + StructCheckContext { + struct_name: "ArchivedTypeWrapper", + field_name: "bytes", + }, + ) + })?; + Ok(()) + } +} + +impl Deserialize for ArchivedTypeWrapper +where + D: Fallible + ?Sized, + D::Error: Source, +{ + #[inline] + fn deserialize(&self, deserializer: &mut D) -> Result { + Ok(TypeWrapper { + type_name: Deserialize::::deserialize(&self.type_name, deserializer)?, + bytes: Deserialize::, D>::deserialize(&self.bytes, deserializer)?, + }) + } +} diff --git a/crates/rspack_cacheable/src/with/as.rs b/crates/rspack_cacheable/src/with/as.rs new file mode 100644 index 00000000000..6167960f68c --- /dev/null +++ b/crates/rspack_cacheable/src/with/as.rs @@ -0,0 +1,71 @@ +use std::any::Any; + +use rkyv::{ + de::Pooling, + rancor::Fallible, + ser::Sharing, + with::{ArchiveWith, DeserializeWith, SerializeWith}, + Archive, Archived, Deserialize, Place, Resolver, Serialize, +}; + +use crate::{context::ContextGuard, DeserializeError, SerializeError}; + +pub trait AsConverter { + fn serialize(data: &T, ctx: &dyn Any) -> Result + where + Self: Sized; + fn deserialize(self, ctx: &dyn Any) -> Result; +} + +pub struct As { + _inner: A, +} + +pub struct AsResolver { + resolver: Resolver, + value: A, +} + +impl ArchiveWith for As +where + A: AsConverter + Archive, +{ + type Archived = Archived; + type Resolver = AsResolver; + + #[inline] + fn resolve_with(_field: &T, resolver: Self::Resolver, out: Place) { + let AsResolver { resolver, value } = resolver; + value.resolve(resolver, out) + } +} + +impl SerializeWith for As +where + A: AsConverter + Archive + Serialize, + S: Fallible + Sharing, +{ + #[inline] + fn serialize_with(field: &T, serializer: &mut S) -> Result { + let ctx = ContextGuard::sharing_context(serializer)?; + let value = >::serialize(field, ctx)?; + Ok(AsResolver { + resolver: value.serialize(serializer)?, + value, + }) + } +} + +impl DeserializeWith, T, D> for As +where + A: AsConverter + Archive, + A::Archived: Deserialize, + D: Fallible + Pooling, +{ + #[inline] + fn deserialize_with(field: &Archived, de: &mut D) -> Result { + let field = A::Archived::deserialize(field, de)?; + let ctx = ContextGuard::pooling_context(de)?; + field.deserialize(ctx) + } +} diff --git a/crates/rspack_cacheable/src/with/as_cacheable.rs b/crates/rspack_cacheable/src/with/as_cacheable.rs new file mode 100644 index 00000000000..8e5585c612b --- /dev/null +++ b/crates/rspack_cacheable/src/with/as_cacheable.rs @@ -0,0 +1,41 @@ +use rkyv::{ + rancor::Fallible, + with::{ArchiveWith, DeserializeWith, SerializeWith}, + Archive, Archived, Deserialize, Place, Resolver, Serialize, +}; + +// reference: rkyv::with::Identity +pub struct AsCacheable; + +impl ArchiveWith for AsCacheable { + type Archived = Archived; + type Resolver = Resolver; + + #[inline] + fn resolve_with(field: &T, resolver: Self::Resolver, out: Place) { + field.resolve(resolver, out) + } +} + +impl SerializeWith for AsCacheable +where + T: Archive + Serialize, + S: ?Sized + Fallible, +{ + #[inline] + fn serialize_with(field: &T, serializer: &mut S) -> Result { + field.serialize(serializer) + } +} + +impl DeserializeWith, T, D> for AsCacheable +where + T: Archive, + T::Archived: Deserialize, + D: ?Sized + Fallible, +{ + #[inline] + fn deserialize_with(field: &Archived, de: &mut D) -> Result { + T::Archived::deserialize(field, de) + } +} diff --git a/crates/rspack_cacheable/src/with/as_inner.rs b/crates/rspack_cacheable/src/with/as_inner.rs new file mode 100644 index 00000000000..5d30ff32614 --- /dev/null +++ b/crates/rspack_cacheable/src/with/as_inner.rs @@ -0,0 +1,159 @@ +use rkyv::{ + munge::munge, + option::ArchivedOption, + rancor::Fallible, + traits::NoUndef, + with::{ArchiveWith, DeserializeWith, SerializeWith}, + Place, +}; + +use crate::with::AsCacheable; + +pub struct AsInner { + _inner: T, +} + +pub trait AsInnerConverter { + type Inner; + fn to_inner(&self) -> &Self::Inner; + fn from_inner(data: Self::Inner) -> Self; +} + +impl ArchiveWith for AsInner +where + T: AsInnerConverter, + A: ArchiveWith, +{ + type Archived = A::Archived; + type Resolver = A::Resolver; + + #[inline] + fn resolve_with(field: &T, resolver: Self::Resolver, out: Place) { + A::resolve_with(field.to_inner(), resolver, out) + } +} + +impl SerializeWith for AsInner +where + T: AsInnerConverter, + S: Fallible + ?Sized, + A: ArchiveWith + SerializeWith, +{ + fn serialize_with(field: &T, s: &mut S) -> Result { + A::serialize_with(field.to_inner(), s) + } +} + +impl DeserializeWith for AsInner +where + T: AsInnerConverter, + A: ArchiveWith + DeserializeWith, + D: ?Sized + Fallible, +{ + fn deserialize_with(field: &A::Archived, d: &mut D) -> Result { + Ok(T::from_inner(A::deserialize_with(field, d)?)) + } +} + +// for Arc +impl AsInnerConverter for std::sync::Arc { + type Inner = T; + fn to_inner(&self) -> &Self::Inner { + self.as_ref() + } + fn from_inner(data: Self::Inner) -> Self { + Self::new(data) + } +} + +// for OnceCell +// rkyv::with::Map +#[repr(u8)] +enum ArchivedOptionTag { + None, + Some, +} +#[repr(C)] +struct ArchivedOptionVariantNone(ArchivedOptionTag); +#[repr(C)] +struct ArchivedOptionVariantSome(ArchivedOptionTag, T); + +// SAFETY: `ArchivedOptionTag` is `repr(u8)` and so always consists of a single +// well-defined byte. +unsafe impl NoUndef for ArchivedOptionTag {} + +impl ArchiveWith> for AsInner +where + A: ArchiveWith, +{ + type Archived = ArchivedOption<>::Archived>; + type Resolver = Option<>::Resolver>; + + fn resolve_with( + field: &once_cell::sync::OnceCell, + resolver: Self::Resolver, + out: Place, + ) { + // port rkyv::with::Map + match resolver { + None => { + let out = unsafe { out.cast_unchecked::() }; + munge!(let ArchivedOptionVariantNone(tag) = out); + tag.write(ArchivedOptionTag::None); + } + Some(resolver) => { + let out = unsafe { + out.cast_unchecked::>::Archived>>() + }; + munge!(let ArchivedOptionVariantSome(tag, out_value) = out); + tag.write(ArchivedOptionTag::Some); + + let value = if let Some(value) = field.get() { + value + } else { + unsafe { + core::hint::unreachable_unchecked(); + } + }; + + A::resolve_with(value, resolver, out_value); + } + } + } +} + +impl SerializeWith, S> for AsInner +where + S: Fallible + ?Sized, + A: ArchiveWith + SerializeWith, +{ + fn serialize_with( + field: &once_cell::sync::OnceCell, + s: &mut S, + ) -> Result { + Ok(match field.get() { + Some(inner) => Some(A::serialize_with(inner, s)?), + None => None, + }) + } +} + +impl + DeserializeWith>::Archived>, once_cell::sync::OnceCell, D> + for AsInner +where + D: Fallible + ?Sized, + A: ArchiveWith + DeserializeWith<>::Archived, O, D>, +{ + fn deserialize_with( + field: &ArchivedOption<>::Archived>, + d: &mut D, + ) -> Result, D::Error> { + match field { + ArchivedOption::Some(value) => Ok(once_cell::sync::OnceCell::with_value( + A::deserialize_with(value, d)?, + )), + ArchivedOption::None => Ok(once_cell::sync::OnceCell::new()), + } + } +} diff --git a/crates/rspack_cacheable/src/with/as_map.rs b/crates/rspack_cacheable/src/with/as_map.rs new file mode 100644 index 00000000000..904e494a881 --- /dev/null +++ b/crates/rspack_cacheable/src/with/as_map.rs @@ -0,0 +1,222 @@ +use std::marker::PhantomData; + +use rkyv::{ + collections::util::Entry as RkyvEntry, + rancor::Fallible, + ser::{Allocator, Writer}, + vec::{ArchivedVec, VecResolver}, + with::{ArchiveWith, DeserializeWith, SerializeWith}, + Archive, Place, Serialize, +}; + +use crate::{with::AsCacheable, DeserializeError}; + +pub struct AsMap { + _key: WK, + _value: WV, +} + +#[allow(clippy::len_without_is_empty)] +pub trait AsMapConverter { + type Key; + type Value; + fn len(&self) -> usize; + fn iter(&self) -> impl Iterator; + fn from( + data: impl Iterator>, + ) -> Result + where + Self: Sized; +} + +pub struct Entry { + key: K, + value: V, + // with for key + _key: PhantomData, + // with for value + _value: PhantomData, +} + +impl Archive for Entry<&'_ K, &'_ V, WK, WV> +where + WK: ArchiveWith, + WV: ArchiveWith, +{ + type Archived = RkyvEntry; + type Resolver = (WK::Resolver, WV::Resolver); + + #[inline] + fn resolve(&self, resolver: Self::Resolver, out: Place) { + let field_ptr = unsafe { &raw mut (*out.ptr()).key }; + let field_out = unsafe { Place::from_field_unchecked(out, field_ptr) }; + WK::resolve_with(self.key, resolver.0, field_out); + let field_ptr = unsafe { &raw mut (*out.ptr()).value }; + let field_out = unsafe { Place::from_field_unchecked(out, field_ptr) }; + WV::resolve_with(self.value, resolver.1, field_out); + } +} + +impl Serialize for Entry<&'_ K, &'_ V, WK, WV> +where + WK: SerializeWith, + WV: SerializeWith, +{ + #[inline] + fn serialize(&self, serializer: &mut S) -> Result { + Ok(( + WK::serialize_with(self.key, serializer)?, + WV::serialize_with(self.value, serializer)?, + )) + } +} + +impl ArchiveWith for AsMap +where + T: AsMapConverter, + WK: ArchiveWith, + WV: ArchiveWith, +{ + type Archived = ArchivedVec>; + type Resolver = VecResolver; + + fn resolve_with(field: &T, resolver: Self::Resolver, out: Place) { + ArchivedVec::resolve_from_len(field.len(), resolver, out) + } +} + +impl SerializeWith for AsMap +where + T: AsMapConverter, + WK: ArchiveWith, + WV: ArchiveWith, + S: Fallible + ?Sized + Allocator + Writer, + for<'a> Entry<&'a K, &'a V, WK, WV>: Serialize, +{ + fn serialize_with(field: &T, s: &mut S) -> Result { + ArchivedVec::serialize_from_slice( + &field + .iter() + .map(|(key, value)| Entry { + key, + value, + _key: PhantomData::::default(), + _value: PhantomData::::default(), + }) + .collect::>(), + s, + ) + } +} + +impl DeserializeWith>, T, D> + for AsMap +where + T: AsMapConverter, + K: std::hash::Hash + Eq, + D: Fallible + ?Sized, + WK: ArchiveWith + DeserializeWith, + WV: ArchiveWith + DeserializeWith, +{ + fn deserialize_with( + field: &ArchivedVec>, + deserializer: &mut D, + ) -> Result { + T::from(field.iter().map(|RkyvEntry { key, value, .. }| { + Ok(( + WK::deserialize_with(key, deserializer)?, + WV::deserialize_with(value, deserializer)?, + )) + })) + } +} + +// for HashMap +impl AsMapConverter for std::collections::HashMap +where + K: std::cmp::Eq + std::hash::Hash, + S: core::hash::BuildHasher + Default, +{ + type Key = K; + type Value = V; + fn len(&self) -> usize { + self.len() + } + fn iter(&self) -> impl Iterator { + self.iter() + } + fn from( + data: impl Iterator>, + ) -> Result { + data.collect::, DeserializeError>>() + } +} + +// for hashlink::LinkedHashMap +impl AsMapConverter for hashlink::LinkedHashMap +where + K: std::cmp::Eq + std::hash::Hash, + S: core::hash::BuildHasher + Default, +{ + type Key = K; + type Value = V; + fn len(&self) -> usize { + self.len() + } + fn iter(&self) -> impl Iterator { + self.iter() + } + fn from( + data: impl Iterator>, + ) -> Result { + data.collect::, DeserializeError>>() + } +} + +// for indexmap::IndexMap +impl AsMapConverter for indexmap::IndexMap +where + K: std::cmp::Eq + std::hash::Hash, + S: core::hash::BuildHasher + Default, +{ + type Key = K; + type Value = V; + fn len(&self) -> usize { + self.len() + } + fn iter(&self) -> impl Iterator { + self.iter() + } + fn from( + data: impl Iterator>, + ) -> Result { + data.collect::, DeserializeError>>() + } +} + +// for DashMap +impl AsMapConverter for dashmap::DashMap +where + K: std::cmp::Eq + std::hash::Hash, + S: core::hash::BuildHasher + Clone + Default, +{ + type Key = K; + type Value = V; + fn len(&self) -> usize { + self.len() + } + fn iter(&self) -> impl Iterator { + dashmap::DashMap::iter(self).map(|item| { + let (key, value) = item.pair(); + let key: *const Self::Key = key; + let value: *const Self::Value = value; + // SAFETY: The key value lifetime should be equal with self + unsafe { (&*key, &*value) } + }) + } + fn from( + data: impl Iterator>, + ) -> Result { + data.collect::, DeserializeError>>() + } +} diff --git a/crates/rspack_cacheable/src/with/as_preset/camino.rs b/crates/rspack_cacheable/src/with/as_preset/camino.rs new file mode 100644 index 00000000000..aabde179812 --- /dev/null +++ b/crates/rspack_cacheable/src/with/as_preset/camino.rs @@ -0,0 +1,41 @@ +use camino::Utf8PathBuf; +use rkyv::{ + rancor::{Fallible, Source}, + ser::Writer, + string::{ArchivedString, StringResolver}, + with::{ArchiveWith, DeserializeWith, SerializeWith}, + Place, +}; + +use super::AsPreset; + +impl ArchiveWith for AsPreset { + type Archived = ArchivedString; + type Resolver = StringResolver; + + #[inline] + fn resolve_with(field: &Utf8PathBuf, resolver: Self::Resolver, out: Place) { + ArchivedString::resolve_from_str(field.as_str(), resolver, out); + } +} + +impl SerializeWith for AsPreset +where + S: ?Sized + Fallible + Writer, + S::Error: Source, +{ + #[inline] + fn serialize_with(field: &Utf8PathBuf, serializer: &mut S) -> Result { + ArchivedString::serialize_from_str(field.as_str(), serializer) + } +} + +impl DeserializeWith for AsPreset +where + D: ?Sized + Fallible, +{ + #[inline] + fn deserialize_with(field: &ArchivedString, _: &mut D) -> Result { + Ok(Utf8PathBuf::from(field.as_str())) + } +} diff --git a/crates/rspack_cacheable/src/with/as_preset/json.rs b/crates/rspack_cacheable/src/with/as_preset/json.rs new file mode 100644 index 00000000000..24b3bac54f9 --- /dev/null +++ b/crates/rspack_cacheable/src/with/as_preset/json.rs @@ -0,0 +1,52 @@ +use json::JsonValue; +use rkyv::{ + rancor::Fallible, + ser::Writer, + string::{ArchivedString, StringResolver}, + with::{ArchiveWith, DeserializeWith, SerializeWith}, + Place, +}; + +use super::AsPreset; +use crate::{DeserializeError, SerializeError}; + +pub struct JsonResolver { + inner: StringResolver, + value: String, +} + +impl ArchiveWith for AsPreset { + type Archived = ArchivedString; + type Resolver = JsonResolver; + + #[inline] + fn resolve_with(_field: &JsonValue, resolver: Self::Resolver, out: Place) { + let JsonResolver { inner, value } = resolver; + ArchivedString::resolve_from_str(&value, inner, out); + } +} + +impl SerializeWith for AsPreset +where + S: Fallible + Writer, +{ + #[inline] + fn serialize_with( + field: &JsonValue, + serializer: &mut S, + ) -> Result { + let value = json::stringify(field.clone()); + let inner = ArchivedString::serialize_from_str(&value, serializer)?; + Ok(JsonResolver { value, inner }) + } +} + +impl DeserializeWith for AsPreset +where + D: Fallible, +{ + #[inline] + fn deserialize_with(field: &ArchivedString, _: &mut D) -> Result { + json::parse(field).map_err(|_| DeserializeError::MessageError("deserialize json value failed")) + } +} diff --git a/crates/rspack_cacheable/src/with/as_preset/lightningcss.rs b/crates/rspack_cacheable/src/with/as_preset/lightningcss.rs new file mode 100644 index 00000000000..4de6c51a884 --- /dev/null +++ b/crates/rspack_cacheable/src/with/as_preset/lightningcss.rs @@ -0,0 +1,56 @@ +use lightningcss::targets::Browsers; +use rkyv::{ + rancor::Fallible, + ser::Writer, + string::{ArchivedString, StringResolver}, + with::{ArchiveWith, DeserializeWith, SerializeWith}, + Place, +}; + +use super::AsPreset; +use crate::{DeserializeError, SerializeError}; + +pub struct BrowsersResolver { + inner: StringResolver, + value: String, +} + +impl ArchiveWith for AsPreset { + type Archived = ArchivedString; + type Resolver = BrowsersResolver; + + #[inline] + fn resolve_with(_field: &Browsers, resolver: Self::Resolver, out: Place) { + let BrowsersResolver { inner, value } = resolver; + ArchivedString::resolve_from_str(&value, inner, out); + } +} + +impl SerializeWith for AsPreset +where + S: Fallible + Writer, +{ + #[inline] + fn serialize_with( + field: &Browsers, + serializer: &mut S, + ) -> Result { + let value = serde_json::to_string(field) + .map_err(|_| SerializeError::MessageError("serialize serde_json value failed"))?; + let inner = ArchivedString::serialize_from_str(&value, serializer)?; + Ok(BrowsersResolver { value, inner }) + } +} + +impl DeserializeWith for AsPreset +where + D: Fallible, +{ + fn deserialize_with( + field: &ArchivedString, + _deserializer: &mut D, + ) -> Result { + serde_json::from_str(field.as_str()) + .map_err(|_| DeserializeError::MessageError("deserialize serde_json value failed")) + } +} diff --git a/crates/rspack_cacheable/src/with/as_preset/mod.rs b/crates/rspack_cacheable/src/with/as_preset/mod.rs new file mode 100644 index 00000000000..6fd04954a6f --- /dev/null +++ b/crates/rspack_cacheable/src/with/as_preset/mod.rs @@ -0,0 +1,10 @@ +mod camino; +mod json; +mod lightningcss; +mod rspack_resolver; +mod rspack_source; +mod serde_json; +mod swc; +mod ustr; + +pub struct AsPreset; diff --git a/crates/rspack_cacheable/src/with/as_preset/rspack_resolver.rs b/crates/rspack_cacheable/src/with/as_preset/rspack_resolver.rs new file mode 100644 index 00000000000..17516fb6f75 --- /dev/null +++ b/crates/rspack_cacheable/src/with/as_preset/rspack_resolver.rs @@ -0,0 +1,107 @@ +use rkyv::{ + bytecheck::{CheckBytes, StructCheckContext}, + rancor::{Fallible, Source, Trace}, + ser::Writer, + string::{ArchivedString, StringResolver}, + with::{ArchiveWith, DeserializeWith, SerializeWith}, + Archive, Deserialize, Place, Portable, +}; +use rspack_resolver::AliasValue; + +use super::AsPreset; + +pub struct ArchivedAliasValue { + is_ignore: bool, + path: ArchivedString, +} + +unsafe impl Portable for ArchivedAliasValue {} + +pub struct AliasValueResolver { + path: StringResolver, +} + +impl ArchiveWith for AsPreset { + type Archived = ArchivedAliasValue; + type Resolver = AliasValueResolver; + + #[inline] + fn resolve_with(field: &AliasValue, resolver: Self::Resolver, out: Place) { + let field_ptr = unsafe { &raw mut (*out.ptr()).is_ignore }; + let field_out = unsafe { Place::from_field_unchecked(out, field_ptr) }; + let is_ignore = matches!(field, AliasValue::Ignore); + is_ignore.resolve((), field_out); + let field_ptr = unsafe { &raw mut (*out.ptr()).path }; + let field_out = unsafe { Place::from_field_unchecked(out, field_ptr) }; + let path = if let AliasValue::Path(path) = field { + path + } else { + "" + }; + ArchivedString::resolve_from_str(path, resolver.path, field_out); + } +} + +impl SerializeWith for AsPreset +where + S: Fallible + Writer + ?Sized, + S::Error: Source, +{ + #[inline] + fn serialize_with(field: &AliasValue, serializer: &mut S) -> Result { + let path = if let AliasValue::Path(path) = field { + path + } else { + "" + }; + Ok(AliasValueResolver { + path: ArchivedString::serialize_from_str(path, serializer)?, + }) + } +} + +unsafe impl CheckBytes for ArchivedAliasValue +where + ArchivedString: CheckBytes, + C: Fallible + ?Sized, + C::Error: Trace, + bool: CheckBytes, +{ + unsafe fn check_bytes(value: *const Self, context: &mut C) -> Result<(), C::Error> { + bool::check_bytes(core::ptr::addr_of!((*value).is_ignore), context).map_err(|e| { + ::trace( + e, + StructCheckContext { + struct_name: "ArchivedAliasValue", + field_name: "is_ignore", + }, + ) + })?; + ArchivedString::check_bytes(core::ptr::addr_of!((*value).path), context).map_err(|e| { + ::trace( + e, + StructCheckContext { + struct_name: "ArchivedAliasValue", + field_name: "path", + }, + ) + })?; + Ok(()) + } +} + +impl DeserializeWith for AsPreset +where + D: ?Sized + Fallible, +{ + fn deserialize_with( + field: &ArchivedAliasValue, + deserializer: &mut D, + ) -> Result { + Ok(if field.is_ignore { + AliasValue::Ignore + } else { + AliasValue::Path(field.path.deserialize(deserializer)?) + }) + } +} diff --git a/crates/rspack_cacheable/src/with/as_preset/rspack_source/mod.rs b/crates/rspack_cacheable/src/with/as_preset/rspack_source/mod.rs new file mode 100644 index 00000000000..89bafbe7774 --- /dev/null +++ b/crates/rspack_cacheable/src/with/as_preset/rspack_source/mod.rs @@ -0,0 +1,103 @@ +use std::sync::Arc; + +use rkyv::{ + rancor::Fallible, + ser::{Allocator, Writer}, + vec::{ArchivedVec, VecResolver}, + with::{ArchiveWith, DeserializeWith, SerializeWith}, + Place, +}; +use rspack_sources::{ + OriginalSource, RawSource, Source, SourceMap, SourceMapSource, SourceMapSourceOptions, +}; + +use super::AsPreset; +use crate::{ + utils::{TypeWrapper, TypeWrapperRef}, + DeserializeError, SerializeError, +}; + +pub struct SourceResolver { + inner: VecResolver, + len: usize, +} + +impl ArchiveWith> for AsPreset { + type Archived = ArchivedVec; + type Resolver = SourceResolver; + + #[inline] + fn resolve_with(_field: &Arc, resolver: Self::Resolver, out: Place) { + ArchivedVec::resolve_from_len(resolver.len, resolver.inner, out) + } +} + +// TODO add cacheable to rspack-sources +impl SerializeWith, S> for AsPreset +where + S: Fallible + Writer + Allocator, +{ + fn serialize_with( + field: &Arc, + serializer: &mut S, + ) -> Result { + let inner = field.as_ref().as_any(); + let bytes = if let Some(raw_source) = inner.downcast_ref::() { + let data = TypeWrapperRef { + type_name: "RawSource", + bytes: &raw_source.buffer(), + }; + crate::to_bytes(&data, &())? + } else if let Some(original_source) = inner.downcast_ref::() { + let source = original_source.source(); + let data = Some(TypeWrapperRef { + type_name: "OriginalSource", + bytes: source.as_bytes(), + }); + crate::to_bytes(&data, &())? + } else if let Some(source_map_source) = inner.downcast_ref::() { + let source = source_map_source.source(); + let data = Some(TypeWrapperRef { + type_name: "SourceMapSource", + bytes: source.as_bytes(), + }); + crate::to_bytes(&data, &())? + } else { + return Err(SerializeError::MessageError("unsupported rspack source")); + }; + Ok(SourceResolver { + inner: ArchivedVec::serialize_from_slice(&bytes, serializer)?, + len: bytes.len(), + }) + } +} + +impl DeserializeWith, Arc, D> for AsPreset +where + D: Fallible, +{ + fn deserialize_with( + field: &ArchivedVec, + _de: &mut D, + ) -> Result, DeserializeError> { + let TypeWrapper { type_name, bytes } = crate::from_bytes(field, &())?; + match type_name.as_str() { + // TODO change to enum + "RawSource" => Ok(Arc::new(RawSource::from(bytes))), + // TODO save original source name + "OriginalSource" => Ok(Arc::new(OriginalSource::new( + "a", + String::from_utf8(bytes).expect("unexpected bytes"), + ))), + "SourceMapSource" => Ok(Arc::new(SourceMapSource::new(SourceMapSourceOptions { + value: String::from_utf8(bytes).expect("unexpected bytes"), + name: String::from("a"), + source_map: SourceMap::default(), + original_source: None, + inner_source_map: None, + remove_original_source: true, + }))), + _ => Err(DeserializeError::MessageError("unsupported box source")), + } + } +} diff --git a/crates/rspack_cacheable/src/with/as_preset/serde_json.rs b/crates/rspack_cacheable/src/with/as_preset/serde_json.rs new file mode 100644 index 00000000000..b8d500c605f --- /dev/null +++ b/crates/rspack_cacheable/src/with/as_preset/serde_json.rs @@ -0,0 +1,51 @@ +use rkyv::{ + rancor::Fallible, + ser::Writer, + string::{ArchivedString, StringResolver}, + with::{ArchiveWith, DeserializeWith, SerializeWith}, + Place, +}; +use serde_json::Value; + +use super::AsPreset; +use crate::{DeserializeError, SerializeError}; + +pub struct SerdeJsonResolver { + inner: StringResolver, + value: String, +} + +impl ArchiveWith for AsPreset { + type Archived = ArchivedString; + type Resolver = SerdeJsonResolver; + + #[inline] + fn resolve_with(_field: &Value, resolver: Self::Resolver, out: Place) { + let SerdeJsonResolver { inner, value } = resolver; + ArchivedString::resolve_from_str(&value, inner, out); + } +} + +impl SerializeWith for AsPreset +where + S: Fallible + Writer, +{ + #[inline] + fn serialize_with(field: &Value, serializer: &mut S) -> Result { + let value = serde_json::to_string(field) + .map_err(|_| SerializeError::MessageError("serialize serde_json value failed"))?; + let inner = ArchivedString::serialize_from_str(&value, serializer)?; + Ok(SerdeJsonResolver { value, inner }) + } +} + +impl DeserializeWith for AsPreset +where + D: Fallible, +{ + #[inline] + fn deserialize_with(field: &ArchivedString, _: &mut D) -> Result { + serde_json::from_str(field) + .map_err(|_| DeserializeError::MessageError("deserialize serde_json value failed")) + } +} diff --git a/crates/rspack_cacheable/src/with/as_preset/swc.rs b/crates/rspack_cacheable/src/with/as_preset/swc.rs new file mode 100644 index 00000000000..9b82df109ee --- /dev/null +++ b/crates/rspack_cacheable/src/with/as_preset/swc.rs @@ -0,0 +1,41 @@ +use rkyv::{ + rancor::{Fallible, Source}, + ser::Writer, + string::{ArchivedString, StringResolver}, + with::{ArchiveWith, DeserializeWith, SerializeWith}, + Place, +}; +use swc_core::ecma::atoms::Atom; + +use super::AsPreset; + +impl ArchiveWith for AsPreset { + type Archived = ArchivedString; + type Resolver = StringResolver; + + #[inline] + fn resolve_with(field: &Atom, resolver: Self::Resolver, out: Place) { + ArchivedString::resolve_from_str(field.as_str(), resolver, out); + } +} + +impl SerializeWith for AsPreset +where + S: ?Sized + Fallible + Writer, + S::Error: Source, +{ + #[inline] + fn serialize_with(field: &Atom, serializer: &mut S) -> Result { + ArchivedString::serialize_from_str(field.as_str(), serializer) + } +} + +impl DeserializeWith for AsPreset +where + D: ?Sized + Fallible, +{ + #[inline] + fn deserialize_with(field: &ArchivedString, _: &mut D) -> Result { + Ok(Atom::from(field.as_str())) + } +} diff --git a/crates/rspack_cacheable/src/with/as_preset/ustr.rs b/crates/rspack_cacheable/src/with/as_preset/ustr.rs new file mode 100644 index 00000000000..b7b564f1b6e --- /dev/null +++ b/crates/rspack_cacheable/src/with/as_preset/ustr.rs @@ -0,0 +1,41 @@ +use rkyv::{ + rancor::{Fallible, Source}, + ser::Writer, + string::{ArchivedString, StringResolver}, + with::{ArchiveWith, DeserializeWith, SerializeWith}, + Place, +}; +use ustr::Ustr; + +use super::AsPreset; + +impl ArchiveWith for AsPreset { + type Archived = ArchivedString; + type Resolver = StringResolver; + + #[inline] + fn resolve_with(field: &Ustr, resolver: Self::Resolver, out: Place) { + ArchivedString::resolve_from_str(field.as_str(), resolver, out); + } +} + +impl SerializeWith for AsPreset +where + S: ?Sized + Fallible + Writer, + S::Error: Source, +{ + #[inline] + fn serialize_with(field: &Ustr, serializer: &mut S) -> Result { + ArchivedString::serialize_from_str(field.as_str(), serializer) + } +} + +impl DeserializeWith for AsPreset +where + D: ?Sized + Fallible, +{ + #[inline] + fn deserialize_with(field: &ArchivedString, _: &mut D) -> Result { + Ok(Ustr::from(field.as_str())) + } +} diff --git a/crates/rspack_cacheable/src/with/as_ref_str.rs b/crates/rspack_cacheable/src/with/as_ref_str.rs new file mode 100644 index 00000000000..8d528d1f554 --- /dev/null +++ b/crates/rspack_cacheable/src/with/as_ref_str.rs @@ -0,0 +1,89 @@ +use rkyv::{ + rancor::{Fallible, Source}, + ser::Writer, + string::{ArchivedString, StringResolver}, + with::{ArchiveWith, DeserializeWith, SerializeWith}, + Place, +}; + +pub struct AsRefStr; + +pub trait AsRefStrConverter { + fn as_str(&self) -> &str; + fn from_str(s: &str) -> Self + where + Self: Sized; +} + +impl ArchiveWith for AsRefStr +where + T: AsRefStrConverter, +{ + type Archived = ArchivedString; + type Resolver = StringResolver; + + #[inline] + fn resolve_with(field: &T, resolver: Self::Resolver, out: Place) { + ArchivedString::resolve_from_str(field.as_str(), resolver, out); + } +} + +impl SerializeWith for AsRefStr +where + T: AsRefStrConverter, + S: ?Sized + Fallible + Writer, + S::Error: Source, +{ + #[inline] + fn serialize_with(field: &T, serializer: &mut S) -> Result { + ArchivedString::serialize_from_str(field.as_str(), serializer) + } +} + +impl DeserializeWith for AsRefStr +where + T: AsRefStrConverter, + D: ?Sized + Fallible, +{ + #[inline] + fn deserialize_with(field: &ArchivedString, _: &mut D) -> Result { + Ok(AsRefStrConverter::from_str(field.as_str())) + } +} + +// for Cow<'static, str> +impl AsRefStrConverter for std::borrow::Cow<'static, str> { + fn as_str(&self) -> &str { + self.as_ref() + } + fn from_str(s: &str) -> Self + where + Self: Sized, + { + Self::from(String::from(s)) + } +} + +// for Arc +use std::sync::{Arc, LazyLock}; + +use dashmap::DashSet; + +pub static CACHED_ARC_STR: LazyLock>> = LazyLock::new(Default::default); +impl AsRefStrConverter for Arc { + fn as_str(&self) -> &str { + self.as_ref() + } + fn from_str(s: &str) -> Self + where + Self: Sized, + { + if let Some(cached_str) = CACHED_ARC_STR.get(s) { + cached_str.clone() + } else { + let s: Arc = Arc::from(s); + CACHED_ARC_STR.insert(s.clone()); + s + } + } +} diff --git a/crates/rspack_cacheable/src/with/as_string.rs b/crates/rspack_cacheable/src/with/as_string.rs new file mode 100644 index 00000000000..82edfa18567 --- /dev/null +++ b/crates/rspack_cacheable/src/with/as_string.rs @@ -0,0 +1,88 @@ +use rkyv::{ + rancor::Fallible, + ser::Writer, + string::{ArchivedString, StringResolver}, + with::{ArchiveWith, DeserializeWith, SerializeWith}, + Place, +}; + +use crate::{DeserializeError, SerializeError}; + +pub struct AsString; + +pub trait AsStringConverter { + fn to_string(&self) -> Result; + fn from_str(s: &str) -> Result + where + Self: Sized; +} + +pub struct AsStringResolver { + inner: StringResolver, + value: String, +} + +impl ArchiveWith for AsString +where + T: AsStringConverter, +{ + type Archived = ArchivedString; + type Resolver = AsStringResolver; + + #[inline] + fn resolve_with(_field: &T, resolver: Self::Resolver, out: Place) { + let AsStringResolver { inner, value } = resolver; + ArchivedString::resolve_from_str(&value, inner, out); + } +} + +impl SerializeWith for AsString +where + T: AsStringConverter, + S: Fallible + Writer, +{ + #[inline] + fn serialize_with(field: &T, serializer: &mut S) -> Result { + let value = field.to_string()?; + let inner = ArchivedString::serialize_from_str(&value, serializer)?; + Ok(AsStringResolver { value, inner }) + } +} + +impl DeserializeWith for AsString +where + T: AsStringConverter, + D: Fallible, +{ + #[inline] + fn deserialize_with(field: &ArchivedString, _: &mut D) -> Result { + AsStringConverter::from_str(field.as_str()) + } +} + +// for pathbuf +use std::path::PathBuf; +impl AsStringConverter for PathBuf { + fn to_string(&self) -> Result { + Ok(self.to_string_lossy().to_string()) + } + fn from_str(s: &str) -> Result + where + Self: Sized, + { + Ok(PathBuf::from(s)) + } +} + +// for Box +impl AsStringConverter for Box { + fn to_string(&self) -> Result { + Ok(str::to_string(self)) + } + fn from_str(s: &str) -> Result + where + Self: Sized, + { + Ok(s.into()) + } +} diff --git a/crates/rspack_cacheable/src/with/as_tuple2.rs b/crates/rspack_cacheable/src/with/as_tuple2.rs new file mode 100644 index 00000000000..28c6c2105ee --- /dev/null +++ b/crates/rspack_cacheable/src/with/as_tuple2.rs @@ -0,0 +1,63 @@ +use rkyv::{ + rancor::Fallible, + tuple::ArchivedTuple2, + with::{ArchiveWith, DeserializeWith, SerializeWith}, + Place, +}; + +use crate::with::AsCacheable; + +pub struct AsTuple2 { + _target: (A, B), +} + +impl ArchiveWith<(K, V)> for AsTuple2 +where + A: ArchiveWith, + B: ArchiveWith, +{ + type Archived = ArchivedTuple2; + type Resolver = ArchivedTuple2; + + #[inline] + fn resolve_with(field: &(K, V), resolver: Self::Resolver, out: Place) { + let field_ptr = unsafe { &raw mut (*out.ptr()).0 }; + let field_out = unsafe { Place::from_field_unchecked(out, field_ptr) }; + A::resolve_with(&field.0, resolver.0, field_out); + let field_ptr = unsafe { &raw mut (*out.ptr()).1 }; + let field_out = unsafe { Place::from_field_unchecked(out, field_ptr) }; + B::resolve_with(&field.1, resolver.1, field_out); + } +} + +impl SerializeWith<(K, V), S> for AsTuple2 +where + A: SerializeWith, + B: SerializeWith, +{ + #[inline] + fn serialize_with(field: &(K, V), serializer: &mut S) -> Result { + Ok(ArchivedTuple2( + A::serialize_with(&field.0, serializer)?, + B::serialize_with(&field.1, serializer)?, + )) + } +} + +impl DeserializeWith, (K, V), D> + for AsTuple2 +where + A: ArchiveWith + DeserializeWith, + B: ArchiveWith + DeserializeWith, + D: ?Sized + Fallible, +{ + fn deserialize_with( + field: &ArchivedTuple2, + deserializer: &mut D, + ) -> Result<(K, V), D::Error> { + Ok(( + A::deserialize_with(&field.0, deserializer)?, + B::deserialize_with(&field.1, deserializer)?, + )) + } +} diff --git a/crates/rspack_cacheable/src/with/as_tuple3.rs b/crates/rspack_cacheable/src/with/as_tuple3.rs new file mode 100644 index 00000000000..7ade245128a --- /dev/null +++ b/crates/rspack_cacheable/src/with/as_tuple3.rs @@ -0,0 +1,72 @@ +use rkyv::{ + rancor::Fallible, + tuple::ArchivedTuple3, + with::{ArchiveWith, DeserializeWith, SerializeWith}, + Place, +}; + +use crate::with::AsCacheable; + +pub struct AsTuple3 { + _target: (A, B, C), +} + +impl ArchiveWith<(K, V, H)> for AsTuple3 +where + A: ArchiveWith, + B: ArchiveWith, + C: ArchiveWith, +{ + type Archived = ArchivedTuple3; + type Resolver = ArchivedTuple3; + + #[inline] + fn resolve_with(field: &(K, V, H), resolver: Self::Resolver, out: Place) { + let field_ptr = unsafe { &raw mut (*out.ptr()).0 }; + let field_out = unsafe { Place::from_field_unchecked(out, field_ptr) }; + A::resolve_with(&field.0, resolver.0, field_out); + let field_ptr = unsafe { &raw mut (*out.ptr()).1 }; + let field_out = unsafe { Place::from_field_unchecked(out, field_ptr) }; + B::resolve_with(&field.1, resolver.1, field_out); + let field_ptr = unsafe { &raw mut (*out.ptr()).2 }; + let field_out = unsafe { Place::from_field_unchecked(out, field_ptr) }; + C::resolve_with(&field.2, resolver.2, field_out); + } +} + +impl SerializeWith<(K, V, H), S> for AsTuple3 +where + A: SerializeWith, + B: SerializeWith, + C: SerializeWith, +{ + #[inline] + fn serialize_with(field: &(K, V, H), serializer: &mut S) -> Result { + Ok(ArchivedTuple3( + A::serialize_with(&field.0, serializer)?, + B::serialize_with(&field.1, serializer)?, + C::serialize_with(&field.2, serializer)?, + )) + } +} + +impl + DeserializeWith, (K, V, H), D> + for AsTuple3 +where + A: ArchiveWith + DeserializeWith, + B: ArchiveWith + DeserializeWith, + C: ArchiveWith + DeserializeWith, + D: ?Sized + Fallible, +{ + fn deserialize_with( + field: &ArchivedTuple3, + deserializer: &mut D, + ) -> Result<(K, V, H), D::Error> { + Ok(( + A::deserialize_with(&field.0, deserializer)?, + B::deserialize_with(&field.1, deserializer)?, + C::deserialize_with(&field.2, deserializer)?, + )) + } +} diff --git a/crates/rspack_cacheable/src/with/as_vec.rs b/crates/rspack_cacheable/src/with/as_vec.rs new file mode 100644 index 00000000000..fe92f29ca1e --- /dev/null +++ b/crates/rspack_cacheable/src/with/as_vec.rs @@ -0,0 +1,144 @@ +use std::marker::PhantomData; + +use rkyv::{ + rancor::Fallible, + ser::{Allocator, Writer}, + vec::{ArchivedVec, VecResolver}, + with::{ArchiveWith, DeserializeWith, SerializeWith}, + Archive, Place, Serialize, +}; + +use crate::{with::AsCacheable, DeserializeError}; + +struct RefWrapper<'o, A, O>(&'o O, PhantomData); + +impl, O> Archive for RefWrapper<'_, A, O> { + type Archived = A::Archived; + type Resolver = A::Resolver; + + fn resolve(&self, resolver: Self::Resolver, out: Place) { + A::resolve_with(self.0, resolver, out) + } +} + +impl Serialize for RefWrapper<'_, A, O> +where + A: ArchiveWith + SerializeWith, + S: Fallible + ?Sized, +{ + fn serialize(&self, s: &mut S) -> Result { + A::serialize_with(self.0, s) + } +} + +pub struct AsVec { + _inner: T, +} + +#[allow(clippy::len_without_is_empty)] +pub trait AsVecConverter { + type Item; + fn len(&self) -> usize; + fn iter(&self) -> impl Iterator; + fn from( + data: impl Iterator>, + ) -> Result + where + Self: Sized; +} + +impl ArchiveWith for AsVec +where + T: AsVecConverter, + A: ArchiveWith, +{ + type Archived = ArchivedVec; + type Resolver = VecResolver; + + fn resolve_with(field: &T, resolver: Self::Resolver, out: Place) { + ArchivedVec::resolve_from_len(field.len(), resolver, out) + } +} + +impl SerializeWith for AsVec +where + T: AsVecConverter, + S: Fallible + Allocator + Writer + ?Sized, + A: ArchiveWith + SerializeWith, +{ + fn serialize_with(field: &T, s: &mut S) -> Result { + let iter = field + .iter() + .map(|value| RefWrapper::<'_, A, O>(value, PhantomData)) + .collect::>(); + + ArchivedVec::serialize_from_slice(&iter, s) + } +} + +impl DeserializeWith, T, D> for AsVec +where + T: AsVecConverter, + D: Fallible, + A: ArchiveWith + DeserializeWith, +{ + fn deserialize_with(field: &ArchivedVec, d: &mut D) -> Result { + T::from(field.iter().map(|item| A::deserialize_with(item, d))) + } +} + +// for Vec +impl AsVecConverter for Vec { + type Item = T; + fn len(&self) -> usize { + self.len() + } + fn iter(&self) -> impl Iterator { + <[T]>::iter(self) + } + fn from( + data: impl Iterator>, + ) -> Result { + data.collect::, DeserializeError>>() + } +} + +// for HashSet +impl AsVecConverter for std::collections::HashSet +where + T: std::cmp::Eq + std::hash::Hash, + S: core::hash::BuildHasher + Default, +{ + type Item = T; + fn len(&self) -> usize { + self.len() + } + fn iter(&self) -> impl Iterator { + self.iter() + } + fn from( + data: impl Iterator>, + ) -> Result { + data.collect::, DeserializeError>>() + } +} + +// for indexmap::IndexSet +impl AsVecConverter for indexmap::IndexSet +where + T: std::cmp::Eq + std::hash::Hash, + S: core::hash::BuildHasher + Default, +{ + type Item = T; + fn len(&self) -> usize { + self.len() + } + fn iter(&self) -> impl Iterator { + self.iter() + } + fn from( + data: impl Iterator>, + ) -> Result { + data.collect::, DeserializeError>>() + } +} diff --git a/crates/rspack_cacheable/src/with/mod.rs b/crates/rspack_cacheable/src/with/mod.rs new file mode 100644 index 00000000000..8c21ee8dffb --- /dev/null +++ b/crates/rspack_cacheable/src/with/mod.rs @@ -0,0 +1,25 @@ +mod r#as; +mod as_cacheable; +mod as_inner; +mod as_map; +mod as_preset; +mod as_ref_str; +mod as_string; +mod as_tuple2; +mod as_tuple3; +mod as_vec; +mod unsupported; + +pub use as_cacheable::AsCacheable; +pub use as_inner::{AsInner, AsInnerConverter}; +pub use as_map::{AsMap, AsMapConverter}; +pub use as_preset::AsPreset; +pub use as_ref_str::{AsRefStr, AsRefStrConverter}; +pub use as_string::{AsString, AsStringConverter}; +pub use as_tuple2::AsTuple2; +pub use as_tuple3::AsTuple3; +pub use as_vec::{AsVec, AsVecConverter}; +pub use r#as::{As, AsConverter}; +pub use rkyv::with::Map as AsOption; +pub use rkyv::with::Skip; +pub use unsupported::Unsupported; diff --git a/crates/rspack_cacheable/src/with/unsupported.rs b/crates/rspack_cacheable/src/with/unsupported.rs new file mode 100644 index 00000000000..66841ffabcb --- /dev/null +++ b/crates/rspack_cacheable/src/with/unsupported.rs @@ -0,0 +1,34 @@ +use rkyv::{ + rancor::Fallible, + with::{ArchiveWith, DeserializeWith, SerializeWith}, + Place, +}; + +use crate::{DeserializeError, SerializeError}; + +pub struct Unsupported; + +impl ArchiveWith for Unsupported { + type Archived = (); + type Resolver = (); + + fn resolve_with(_: &F, _: Self::Resolver, _: Place) {} +} + +impl SerializeWith for Unsupported +where + S: Fallible, +{ + fn serialize_with(_: &F, _: &mut S) -> Result<(), SerializeError> { + Err(SerializeError::UnsupportedField) + } +} + +impl DeserializeWith<(), F, D> for Unsupported +where + D: Fallible, +{ + fn deserialize_with(_: &(), _: &mut D) -> Result { + Err(DeserializeError::UnsupportedField) + } +} diff --git a/crates/rspack_cacheable_test/Cargo.toml b/crates/rspack_cacheable_test/Cargo.toml new file mode 100644 index 00000000000..16ea7992afa --- /dev/null +++ b/crates/rspack_cacheable_test/Cargo.toml @@ -0,0 +1,21 @@ +[package] +edition = "2021" +license = "MIT" +name = "rspack_cacheable_test" +repository = "https://github.com/web-infra-dev/rspack" +version = "0.1.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +camino = { workspace = true } +dashmap = { workspace = true } +json = { workspace = true } +lightningcss = { workspace = true } +once_cell = { workspace = true } +rspack_cacheable = { path = "../rspack_cacheable" } +rspack_resolver = { workspace = true } +rustc-hash = { workspace = true } +serde_json = { workspace = true } +swc_core = { workspace = true, features = ["ecma_ast"] } +ustr = { workspace = true } diff --git a/crates/rspack_cacheable_test/LICENSE b/crates/rspack_cacheable_test/LICENSE new file mode 100644 index 00000000000..46310101ad8 --- /dev/null +++ b/crates/rspack_cacheable_test/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2022-present Bytedance, Inc. and its affiliates. + + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/crates/rspack_cacheable_test/tests/context.rs b/crates/rspack_cacheable_test/tests/context.rs new file mode 100644 index 00000000000..1aa6959d344 --- /dev/null +++ b/crates/rspack_cacheable_test/tests/context.rs @@ -0,0 +1,60 @@ +use std::{any::Any, sync::Arc}; + +use rspack_cacheable::{ + cacheable, from_bytes, to_bytes, + with::{As, AsConverter}, + DeserializeError, SerializeError, +}; + +#[derive(Debug, PartialEq, Eq)] +struct CompilerOptions { + data: usize, +} + +#[derive(Debug)] +struct Context { + option: Arc, +} + +#[cacheable] +struct FromContext; + +impl AsConverter> for FromContext { + fn serialize(_data: &Arc, _ctx: &dyn Any) -> Result { + Ok(FromContext) + } + fn deserialize(self, ctx: &dyn Any) -> Result, DeserializeError> { + let Some(ctx) = ctx.downcast_ref::() else { + return Err(DeserializeError::MessageError("context not match")); + }; + Ok(ctx.option.clone()) + } +} + +#[cacheable] +#[derive(Debug, PartialEq, Eq)] +struct Module { + #[cacheable(with=As)] + compiler_option: Arc, + name: String, +} + +#[test] +fn test_context() { + let context = Context { + option: Arc::new(CompilerOptions { data: 1 }), + }; + let module = Module { + compiler_option: context.option.clone(), + name: String::from("a"), + }; + + let bytes = to_bytes(&module, &()).unwrap(); + + assert!(matches!( + from_bytes::(&bytes, &()), + Err(DeserializeError::MessageError("context not match")) + )); + let new_module: Module = from_bytes(&bytes, &context).unwrap(); + assert_eq!(module, new_module); +} diff --git a/crates/rspack_cacheable_test/tests/macro/cacheable.rs b/crates/rspack_cacheable_test/tests/macro/cacheable.rs new file mode 100644 index 00000000000..9120931c5fe --- /dev/null +++ b/crates/rspack_cacheable_test/tests/macro/cacheable.rs @@ -0,0 +1,158 @@ +use rspack_cacheable::{ + cacheable, from_bytes, to_bytes, + with::{AsMap, AsRefStr, AsRefStrConverter}, +}; + +#[test] +fn basic_macro_feature() { + #[cacheable] + #[derive(Debug, PartialEq, Eq)] + struct Person { + name: String, + } + + let a = Person { + name: String::from("a"), + }; + let bytes = to_bytes(&a, &()).unwrap(); + let deserialize_a = from_bytes(&bytes, &()).unwrap(); + assert_eq!(a, deserialize_a); +} + +#[test] +fn hashable_attr() { + use rustc_hash::FxHashSet as HashSet; + #[cacheable(hashable)] + #[derive(Debug, Hash, PartialEq, Eq)] + struct Person { + name: String, + } + + let mut a = HashSet::default(); + a.insert(Person { + name: String::from("a"), + }); + let bytes = to_bytes(&a, &()).unwrap(); + let deserialize_a: HashSet = from_bytes(&bytes, &()).unwrap(); + assert_eq!(a, deserialize_a); +} + +#[test] +fn with_attr() { + #[derive(Debug, PartialEq, Eq)] + struct UnCacheable; + + #[cacheable(with=AsRefStr)] + #[derive(Debug, PartialEq, Eq)] + struct Person { + name: String, + uncacheable: UnCacheable, + } + impl AsRefStrConverter for Person { + fn as_str(&self) -> &str { + &self.name + } + fn from_str(s: &str) -> Self { + Self { + name: String::from(s), + uncacheable: UnCacheable, + } + } + } + + let a = Person { + name: String::from("a"), + uncacheable: UnCacheable, + }; + let bytes = to_bytes(&a, &()).unwrap(); + let deserialize_a = from_bytes(&bytes, &()).unwrap(); + assert_eq!(a, deserialize_a); +} + +#[test] +fn with_attr_with_generics() { + #[derive(Debug, Default, PartialEq, Eq)] + struct UnCacheable; + + #[cacheable(with=AsRefStr)] + #[derive(Debug, PartialEq, Eq)] + struct Person + where + T: Default, + { + name: String, + uncacheable: T, + } + impl AsRefStrConverter for Person { + fn as_str(&self) -> &str { + &self.name + } + fn from_str(s: &str) -> Self { + Self { + name: String::from(s), + uncacheable: Default::default(), + } + } + } + + let a = Person { + name: String::from("a"), + uncacheable: UnCacheable, + }; + let bytes = to_bytes(&a, &()).unwrap(); + let deserialize_a = from_bytes(&bytes, &()).unwrap(); + assert_eq!(a, deserialize_a); +} + +#[test] +fn omit_bounds_attr() { + use dashmap::DashMap; + + #[cacheable] + #[derive(Debug, Clone)] + struct Value { + id: String, + #[cacheable(omit_bounds, with=AsMap)] + map: DashMap, + #[cacheable(omit_bounds)] + children: Vec, + } + + let map = DashMap::default(); + map.insert( + String::from("a"), + Value { + id: String::from("a"), + map: DashMap::default(), + children: vec![], + }, + ); + map.insert( + String::from("b"), + Value { + id: String::from("b"), + map: DashMap::default(), + children: vec![], + }, + ); + let value = Value { + id: String::from("root"), + children: map.iter().map(|item| item.value().clone()).collect(), + map, + }; + let bytes = to_bytes(&value, &()).unwrap(); + let new_value: Value = from_bytes(&bytes, &()).unwrap(); + + assert_eq!(value.id, new_value.id); + for (key, value) in new_value.map { + assert!(key == "a" || key == "b"); + assert!(value.id == "a" || value.id == "b"); + assert_eq!(value.map.len(), 0); + assert_eq!(value.children.len(), 0); + } + for value in new_value.children { + assert!(value.id == "a" || value.id == "b"); + assert_eq!(value.map.len(), 0); + assert_eq!(value.children.len(), 0); + } +} diff --git a/crates/rspack_cacheable_test/tests/macro/cacheable_dyn.rs b/crates/rspack_cacheable_test/tests/macro/cacheable_dyn.rs new file mode 100644 index 00000000000..421db0f7823 --- /dev/null +++ b/crates/rspack_cacheable_test/tests/macro/cacheable_dyn.rs @@ -0,0 +1,139 @@ +use rspack_cacheable::{cacheable, cacheable_dyn, from_bytes, to_bytes}; + +#[test] +fn test_cacheable_dyn_macro() { + struct Context; + + #[cacheable_dyn] + trait Animal { + fn color(&self) -> &str; + fn name(&self) -> &str; + } + + #[cacheable] + struct Dog { + color: String, + } + #[cacheable_dyn] + impl Animal for Dog { + fn color(&self) -> &str { + &self.color + } + fn name(&self) -> &str { + "dog" + } + } + + #[cacheable] + struct Cat { + color: String, + } + #[cacheable_dyn] + impl Animal for Cat { + fn color(&self) -> &str { + &self.color + } + fn name(&self) -> &str { + "cat" + } + } + + #[cacheable] + struct Data { + animal: Box, + } + + let dog_data = Data { + animal: Box::new(Dog { + color: String::from("black"), + }), + }; + assert_eq!(dog_data.animal.name(), "dog"); + assert_eq!(dog_data.animal.color(), "black"); + let ctx = Context {}; + let bytes = to_bytes(&dog_data, &ctx).unwrap(); + let deserialize_data = from_bytes::(&bytes, &ctx).unwrap(); + assert_eq!(deserialize_data.animal.name(), "dog"); + assert_eq!(deserialize_data.animal.color(), "black"); + + let cat_data = Data { + animal: Box::new(Cat { + color: String::from("white"), + }), + }; + assert_eq!(cat_data.animal.name(), "cat"); + assert_eq!(cat_data.animal.color(), "white"); + let ctx = Context {}; + let bytes = to_bytes(&cat_data, &ctx).unwrap(); + let deserialize_data = from_bytes::(&bytes, &ctx).unwrap(); + assert_eq!(deserialize_data.animal.name(), "cat"); + assert_eq!(deserialize_data.animal.color(), "white"); +} + +#[test] +fn test_cacheable_dyn_macro_with_generics() { + struct Context; + + #[cacheable_dyn] + trait Animal: Send + Sync + where + T: Send, + { + fn color(&self) -> &str; + fn name(&self) -> T; + } + + #[cacheable] + struct Dog { + color: String, + } + #[cacheable_dyn] + impl Animal<&'static str> for Dog { + fn color(&self) -> &str { + &self.color + } + fn name(&self) -> &'static str { + "dog" + } + } + + #[cacheable] + struct Cat { + color: String, + } + #[cacheable_dyn] + impl Animal for Cat { + fn color(&self) -> &str { + &self.color + } + fn name(&self) -> String { + String::from("cat") + } + } + + #[cacheable] + struct Data { + animal_1: Box>, + animal_2: Box>, + } + + let data = Data { + animal_1: Box::new(Dog { + color: String::from("black"), + }), + animal_2: Box::new(Cat { + color: String::from("white"), + }), + }; + assert_eq!(data.animal_1.name(), "dog"); + assert_eq!(data.animal_1.color(), "black"); + assert_eq!(data.animal_2.name(), "cat"); + assert_eq!(data.animal_2.color(), "white"); + let ctx = Context {}; + let bytes = to_bytes(&data, &ctx).unwrap(); + let deserialize_data = from_bytes::(&bytes, &ctx).unwrap(); + assert_eq!(deserialize_data.animal_1.name(), "dog"); + assert_eq!(deserialize_data.animal_1.color(), "black"); + assert_eq!(deserialize_data.animal_2.name(), "cat"); + assert_eq!(deserialize_data.animal_2.color(), "white"); +} diff --git a/crates/rspack_cacheable_test/tests/macro/manual_cacheable.rs b/crates/rspack_cacheable_test/tests/macro/manual_cacheable.rs new file mode 100644 index 00000000000..9833026614c --- /dev/null +++ b/crates/rspack_cacheable_test/tests/macro/manual_cacheable.rs @@ -0,0 +1,258 @@ +use rspack_cacheable::{ + from_bytes, to_bytes, + with::{AsMap, AsRefStr, AsRefStrConverter}, +}; + +#[test] +fn basic_macro_feature() { + #[derive( + rspack_cacheable::__private::rkyv::Archive, + rspack_cacheable::__private::rkyv::Deserialize, + rspack_cacheable::__private::rkyv::Serialize, + )] + #[rkyv(crate=rspack_cacheable::__private::rkyv)] + #[derive(Debug, PartialEq, Eq)] + struct Person { + name: String, + } + + let a = Person { + name: String::from("a"), + }; + let bytes = to_bytes(&a, &()).unwrap(); + let deserialize_a = from_bytes(&bytes, &()).unwrap(); + assert_eq!(a, deserialize_a); +} + +#[test] +fn hashable_attr() { + use rustc_hash::FxHashSet as HashSet; + #[derive( + rspack_cacheable::__private::rkyv::Archive, + rspack_cacheable::__private::rkyv::Deserialize, + rspack_cacheable::__private::rkyv::Serialize, + )] + #[rkyv(crate=rspack_cacheable::__private::rkyv)] + #[rkyv(derive(Hash, PartialEq, Eq))] + #[derive(Debug, Hash, PartialEq, Eq)] + struct Person { + name: String, + } + + let mut a = HashSet::default(); + a.insert(Person { + name: String::from("a"), + }); + let bytes = to_bytes(&a, &()).unwrap(); + let deserialize_a: HashSet = from_bytes(&bytes, &()).unwrap(); + assert_eq!(a, deserialize_a); +} + +#[test] +fn with_attr() { + #[derive(Debug, PartialEq, Eq)] + struct UnCacheable; + + #[derive(Debug, PartialEq, Eq)] + struct Person { + name: String, + uncacheable: UnCacheable, + } + impl AsRefStrConverter for Person { + fn as_str(&self) -> &str { + &self.name + } + fn from_str(s: &str) -> Self { + Self { + name: String::from(s), + uncacheable: UnCacheable, + } + } + } + + #[allow(non_upper_case_globals)] + const _: () = { + use rkyv::{ + rancor::Fallible, + with::{ArchiveWith, DeserializeWith, SerializeWith}, + Archive, Deserialize, Place, Serialize, + }; + use rspack_cacheable::__private::rkyv; + impl Archive for Person { + type Archived = >::Archived; + type Resolver = >::Resolver; + #[inline] + fn resolve(&self, resolver: Self::Resolver, out: Place) { + >::resolve_with(self, resolver, out) + } + } + impl Serialize for Person + where + S: Fallible + ?Sized, + AsRefStr: SerializeWith, + { + #[inline] + fn serialize(&self, serializer: &mut S) -> Result { + AsRefStr::serialize_with(self, serializer) + } + } + impl Deserialize for >::Archived + where + D: Fallible + ?Sized, + AsRefStr: DeserializeWith<>::Archived, Person, D>, + { + #[inline] + fn deserialize(&self, deserializer: &mut D) -> Result { + AsRefStr::deserialize_with(self, deserializer) + } + } + }; + + let a = Person { + name: String::from("a"), + uncacheable: UnCacheable, + }; + let bytes = to_bytes(&a, &()).unwrap(); + let deserialize_a = from_bytes(&bytes, &()).unwrap(); + assert_eq!(a, deserialize_a); +} + +#[test] +fn with_attr_with_generics() { + #[derive(Debug, Default, PartialEq, Eq)] + struct UnCacheable; + + #[derive(Debug, PartialEq, Eq)] + struct Person { + name: String, + uncacheable: T, + } + impl AsRefStrConverter for Person { + fn as_str(&self) -> &str { + &self.name + } + fn from_str(s: &str) -> Self { + Self { + name: String::from(s), + uncacheable: Default::default(), + } + } + } + + #[allow(non_upper_case_globals)] + const _: () = { + use rkyv::{ + rancor::Fallible, + with::{ArchiveWith, DeserializeWith, SerializeWith}, + Archive, Deserialize, Place, Serialize, + }; + use rspack_cacheable::__private::rkyv; + impl Archive for Person { + type Archived = >>::Archived; + type Resolver = >>::Resolver; + #[inline] + fn resolve(&self, resolver: Self::Resolver, out: Place) { + >>::resolve_with(self, resolver, out) + } + } + impl Serialize for Person + where + S: Fallible + ?Sized, + AsRefStr: SerializeWith, S>, + { + #[inline] + fn serialize(&self, serializer: &mut S) -> Result { + AsRefStr::serialize_with(self, serializer) + } + } + impl Deserialize, D> for >>::Archived + where + D: Fallible + ?Sized, + AsRefStr: DeserializeWith<>>::Archived, Person, D>, + { + #[inline] + fn deserialize(&self, deserializer: &mut D) -> Result, D::Error> { + AsRefStr::deserialize_with(self, deserializer) + } + } + }; + + let a = Person { + name: String::from("a"), + uncacheable: UnCacheable, + }; + let bytes = to_bytes(&a, &()).unwrap(); + let deserialize_a = from_bytes(&bytes, &()).unwrap(); + assert_eq!(a, deserialize_a); +} + +#[test] +fn omit_bounds_attr() { + use dashmap::DashMap; + + // reference: https://github.com/rkyv/rkyv/blob/739f53928d7c9c870b1d2072a9b73c80466f2a87/rkyv/examples/json_like_schema.rs#L45 + #[derive( + rspack_cacheable::__private::rkyv::Archive, + rspack_cacheable::__private::rkyv::Deserialize, + rspack_cacheable::__private::rkyv::Serialize, + )] + #[rkyv(crate=rspack_cacheable::__private::rkyv)] + #[rkyv(serialize_bounds( + __S: rspack_cacheable::__private::rkyv::ser::Writer + rspack_cacheable::__private::rkyv::ser::Allocator + rspack_cacheable::__private::rkyv::rancor::Fallible, + ))] + #[rkyv(deserialize_bounds( + __D: rspack_cacheable::__private::rkyv::rancor::Fallible + ))] + #[rkyv(bytecheck( + bounds( + __C: rspack_cacheable::__private::rkyv::validation::ArchiveContext + rspack_cacheable::__private::rkyv::rancor::Fallible, + ) + ))] + #[derive(Debug, Clone)] + struct Value { + id: String, + #[rkyv(omit_bounds)] + #[rkyv(with=AsMap)] + map: DashMap, + #[rkyv(omit_bounds)] + children: Vec, + } + + let map = DashMap::default(); + map.insert( + String::from("a"), + Value { + id: String::from("a"), + map: DashMap::default(), + children: vec![], + }, + ); + map.insert( + String::from("b"), + Value { + id: String::from("b"), + map: DashMap::default(), + children: vec![], + }, + ); + let value = Value { + id: String::from("root"), + children: map.iter().map(|item| item.value().clone()).collect(), + map, + }; + let bytes = to_bytes(&value, &()).unwrap(); + let new_value: Value = from_bytes(&bytes, &()).unwrap(); + + assert_eq!(value.id, new_value.id); + for (key, value) in new_value.map { + assert!(key == "a" || key == "b"); + assert!(value.id == "a" || value.id == "b"); + assert_eq!(value.map.len(), 0); + assert_eq!(value.children.len(), 0); + } + for value in new_value.children { + assert!(value.id == "a" || value.id == "b"); + assert_eq!(value.map.len(), 0); + assert_eq!(value.children.len(), 0); + } +} diff --git a/crates/rspack_cacheable_test/tests/macro/manual_cacheable_dyn.rs b/crates/rspack_cacheable_test/tests/macro/manual_cacheable_dyn.rs new file mode 100644 index 00000000000..6ae4fe67684 --- /dev/null +++ b/crates/rspack_cacheable_test/tests/macro/manual_cacheable_dyn.rs @@ -0,0 +1,280 @@ +use rspack_cacheable::{cacheable, from_bytes, to_bytes}; + +#[test] +fn test_manual_cacheable_dyn_macro() { + struct Context; + + trait Animal: rspack_cacheable::r#dyn::SerializeDyn { + fn color(&self) -> &str; + fn name(&self) -> &str; + + #[doc(hidden)] + fn __dyn_id(&self) -> u64; + } + + const _: () = { + use std::alloc::{Layout, LayoutError}; + + use rspack_cacheable::__private::rkyv::{ + bytecheck::CheckBytes, + ptr_meta, + traits::{ArchivePointee, LayoutRaw}, + ArchiveUnsized, ArchivedMetadata, DeserializeUnsized, Portable, SerializeUnsized, + }; + use rspack_cacheable::{ + r#dyn::{validation::CHECK_BYTES_REGISTRY, ArchivedDynMetadata, DeserializeDyn}, + DeserializeError, Deserializer, SerializeError, Serializer, Validator, + }; + + unsafe impl ptr_meta::Pointee for dyn Animal { + type Metadata = ptr_meta::DynMetadata; + } + + impl ArchiveUnsized for dyn Animal { + type Archived = dyn DeserializeAnimal; + + fn archived_metadata(&self) -> ArchivedMetadata { + ArchivedDynMetadata::new(Animal::__dyn_id(self)) + } + } + + impl LayoutRaw for dyn Animal { + fn layout_raw( + metadata: ::Metadata, + ) -> Result { + Ok(metadata.layout()) + } + } + + impl SerializeUnsized> for dyn Animal { + fn serialize_unsized(&self, serializer: &mut Serializer) -> Result { + self.serialize_dyn(serializer) + } + } + + pub trait DeserializeAnimal: DeserializeDyn + Portable {} + unsafe impl ptr_meta::Pointee for dyn DeserializeAnimal { + type Metadata = ptr_meta::DynMetadata; + } + + impl + Portable> DeserializeAnimal for T {} + + impl ArchivePointee for dyn DeserializeAnimal { + type ArchivedMetadata = ArchivedDynMetadata; + + fn pointer_metadata( + archived: &Self::ArchivedMetadata, + ) -> ::Metadata { + archived.lookup_metadata() + } + } + + impl DeserializeUnsized for dyn DeserializeAnimal { + unsafe fn deserialize_unsized( + &self, + deserializer: &mut Deserializer, + out: *mut dyn Animal, + ) -> Result<(), DeserializeError> { + self.deserialize_dyn(deserializer, out) + } + + fn deserialize_metadata(&self) -> ::Metadata { + self.deserialized_pointer_metadata() + } + } + + impl LayoutRaw for dyn DeserializeAnimal { + fn layout_raw( + metadata: ::Metadata, + ) -> Result { + Ok(metadata.layout()) + } + } + + // CheckBytes + unsafe impl CheckBytes> for dyn DeserializeAnimal { + #[inline] + unsafe fn check_bytes( + value: *const Self, + context: &mut Validator, + ) -> Result<(), DeserializeError> { + let vtable: usize = std::mem::transmute(ptr_meta::metadata(value)); + if let Some(check_bytes_dyn) = CHECK_BYTES_REGISTRY.get(&vtable) { + check_bytes_dyn(value.cast(), context)?; + Ok(()) + } else { + Err(DeserializeError::DynCheckBytesNotRegister) + } + } + } + }; + + #[cacheable] + struct Dog { + color: String, + } + + static __DYN_ID_DOG_ANIMAL: std::sync::LazyLock = std::sync::LazyLock::new(|| { + use std::hash::{DefaultHasher, Hash, Hasher}; + let mut hasher = DefaultHasher::new(); + module_path!().hash(&mut hasher); + line!().hash(&mut hasher); + hasher.finish() + }); + + impl Animal for Dog { + fn color(&self) -> &str { + &self.color + } + fn name(&self) -> &str { + "dog" + } + + fn __dyn_id(&self) -> u64 { + *__DYN_ID_DOG_ANIMAL + } + } + + const _: () = { + use rspack_cacheable::__private::{ + inventory, + rkyv::{ptr_meta, ArchiveUnsized, Archived, Deserialize, DeserializeUnsized}, + }; + use rspack_cacheable::{ + r#dyn::{ + validation::{default_check_bytes_dyn, CheckBytesEntry}, + DeserializeDyn, DynEntry, + }, + DeserializeError, Deserializer, + }; + + fn get_vtable() -> usize { + unsafe { + core::mem::transmute(ptr_meta::metadata( + core::ptr::null::>() as *const ::Archived + )) + } + } + inventory::submit! { DynEntry::new(*__DYN_ID_DOG_ANIMAL, get_vtable()) } + inventory::submit! { CheckBytesEntry::new(get_vtable(), default_check_bytes_dyn::>) } + + impl DeserializeDyn for ArchivedDog + where + ArchivedDog: Deserialize, + { + fn deserialize_dyn( + &self, + deserializer: &mut Deserializer, + out: *mut dyn Animal, + ) -> Result<(), DeserializeError> { + unsafe { + >::deserialize_unsized(self, deserializer, out.cast()) + } + } + + fn deserialized_pointer_metadata(&self) -> ptr_meta::DynMetadata { + ptr_meta::metadata(core::ptr::null::() as *const dyn Animal) + } + } + }; + + #[cacheable] + struct Cat { + color: String, + } + + static __DYN_ID_CAT_ANIMAL: std::sync::LazyLock = std::sync::LazyLock::new(|| { + use std::hash::{DefaultHasher, Hash, Hasher}; + let mut hasher = DefaultHasher::new(); + module_path!().hash(&mut hasher); + line!().hash(&mut hasher); + hasher.finish() + }); + + impl Animal for Cat { + fn color(&self) -> &str { + &self.color + } + fn name(&self) -> &str { + "cat" + } + + fn __dyn_id(&self) -> u64 { + *__DYN_ID_CAT_ANIMAL + } + } + + const _: () = { + use rspack_cacheable::__private::{ + inventory, + rkyv::{ptr_meta, ArchiveUnsized, Archived, Deserialize, DeserializeUnsized}, + }; + use rspack_cacheable::{ + r#dyn::{ + validation::{default_check_bytes_dyn, CheckBytesEntry}, + DeserializeDyn, DynEntry, + }, + DeserializeError, Deserializer, + }; + + fn get_vtable() -> usize { + unsafe { + core::mem::transmute(ptr_meta::metadata( + core::ptr::null::>() as *const ::Archived + )) + } + } + inventory::submit! { DynEntry::new(*__DYN_ID_CAT_ANIMAL, get_vtable()) } + inventory::submit! { CheckBytesEntry::new(get_vtable(), default_check_bytes_dyn::>) } + + impl DeserializeDyn for ArchivedCat + where + ArchivedCat: Deserialize, + { + fn deserialize_dyn( + &self, + deserializer: &mut Deserializer, + out: *mut dyn Animal, + ) -> Result<(), DeserializeError> { + unsafe { + >::deserialize_unsized(self, deserializer, out.cast()) + } + } + + fn deserialized_pointer_metadata(&self) -> ptr_meta::DynMetadata { + ptr_meta::metadata(core::ptr::null::() as *const dyn Animal) + } + } + }; + + #[cacheable] + struct Data { + animal: Box, + } + + let dog_data = Data { + animal: Box::new(Dog { + color: String::from("black"), + }), + }; + assert_eq!(dog_data.animal.name(), "dog"); + assert_eq!(dog_data.animal.color(), "black"); + let ctx = Context {}; + let bytes = to_bytes(&dog_data, &ctx).unwrap(); + let deserialize_data = from_bytes::(&bytes, &ctx).unwrap(); + assert_eq!(deserialize_data.animal.name(), "dog"); + assert_eq!(deserialize_data.animal.color(), "black"); + + let cat_data = Data { + animal: Box::new(Cat { + color: String::from("white"), + }), + }; + assert_eq!(cat_data.animal.name(), "cat"); + assert_eq!(cat_data.animal.color(), "white"); + let ctx = Context {}; + let bytes = to_bytes(&cat_data, &ctx).unwrap(); + let deserialize_data = from_bytes::(&bytes, &ctx).unwrap(); + assert_eq!(deserialize_data.animal.name(), "cat"); + assert_eq!(deserialize_data.animal.color(), "white"); +} diff --git a/crates/rspack_cacheable_test/tests/macro/manual_cacheable_dyn_with_generics.rs b/crates/rspack_cacheable_test/tests/macro/manual_cacheable_dyn_with_generics.rs new file mode 100644 index 00000000000..55d070eee5e --- /dev/null +++ b/crates/rspack_cacheable_test/tests/macro/manual_cacheable_dyn_with_generics.rs @@ -0,0 +1,271 @@ +use rspack_cacheable::{cacheable, from_bytes, to_bytes}; + +#[test] +fn test_manual_cacheable_dyn_macro_with_generics() { + struct Context; + + trait Animal: rspack_cacheable::r#dyn::SerializeDyn { + fn color(&self) -> &str; + fn name(&self) -> T; + + #[doc(hidden)] + fn __dyn_id(&self) -> u64; + } + + const _: () = { + use std::alloc::{Layout, LayoutError}; + + use rspack_cacheable::__private::rkyv::{ + bytecheck::CheckBytes, + ptr_meta, + traits::{ArchivePointee, LayoutRaw}, + ArchiveUnsized, ArchivedMetadata, DeserializeUnsized, Portable, SerializeUnsized, + }; + use rspack_cacheable::{ + r#dyn::{validation::CHECK_BYTES_REGISTRY, ArchivedDynMetadata, DeserializeDyn}, + DeserializeError, Deserializer, SerializeError, Serializer, Validator, + }; + + unsafe impl ptr_meta::Pointee for dyn Animal { + type Metadata = ptr_meta::DynMetadata; + } + + impl ArchiveUnsized for dyn Animal { + type Archived = dyn DeserializeAnimal; + + fn archived_metadata(&self) -> ArchivedMetadata { + ArchivedDynMetadata::new(Animal::__dyn_id(self)) + } + } + + impl LayoutRaw for dyn Animal { + fn layout_raw( + metadata: ::Metadata, + ) -> Result { + Ok(metadata.layout()) + } + } + + impl SerializeUnsized> for dyn Animal { + fn serialize_unsized(&self, serializer: &mut Serializer) -> Result { + self.serialize_dyn(serializer) + } + } + + pub trait DeserializeAnimal: DeserializeDyn> + Portable {} + unsafe impl ptr_meta::Pointee for dyn DeserializeAnimal { + type Metadata = ptr_meta::DynMetadata; + } + + impl> + Portable> DeserializeAnimal for O {} + + impl ArchivePointee for dyn DeserializeAnimal { + type ArchivedMetadata = ArchivedDynMetadata; + + fn pointer_metadata( + archived: &Self::ArchivedMetadata, + ) -> ::Metadata { + archived.lookup_metadata() + } + } + + impl DeserializeUnsized, Deserializer> for dyn DeserializeAnimal { + unsafe fn deserialize_unsized( + &self, + deserializer: &mut Deserializer, + out: *mut dyn Animal, + ) -> Result<(), DeserializeError> { + self.deserialize_dyn(deserializer, out) + } + + fn deserialize_metadata(&self) -> as ptr_meta::Pointee>::Metadata { + self.deserialized_pointer_metadata() + } + } + + impl LayoutRaw for dyn DeserializeAnimal { + fn layout_raw( + metadata: ::Metadata, + ) -> Result { + Ok(metadata.layout()) + } + } + + // CheckBytes + unsafe impl CheckBytes> for dyn DeserializeAnimal { + #[inline] + unsafe fn check_bytes( + value: *const Self, + context: &mut Validator, + ) -> Result<(), DeserializeError> { + let vtable: usize = std::mem::transmute(ptr_meta::metadata(value)); + if let Some(check_bytes_dyn) = CHECK_BYTES_REGISTRY.get(&vtable) { + check_bytes_dyn(value.cast(), context)?; + Ok(()) + } else { + Err(DeserializeError::DynCheckBytesNotRegister) + } + } + } + }; + + #[cacheable] + struct Dog { + color: String, + } + + static __DYN_ID_DOG_ANIMAL: std::sync::LazyLock = std::sync::LazyLock::new(|| { + use std::hash::{DefaultHasher, Hash, Hasher}; + let mut hasher = DefaultHasher::new(); + module_path!().hash(&mut hasher); + line!().hash(&mut hasher); + hasher.finish() + }); + + impl Animal<&'static str> for Dog { + fn color(&self) -> &str { + &self.color + } + fn name(&self) -> &'static str { + "dog" + } + fn __dyn_id(&self) -> u64 { + *__DYN_ID_DOG_ANIMAL + } + } + + const _: () = { + use rspack_cacheable::__private::{ + inventory, + rkyv::{ptr_meta, ArchiveUnsized, Archived, Deserialize, DeserializeUnsized}, + }; + use rspack_cacheable::{ + r#dyn::{ + validation::{default_check_bytes_dyn, CheckBytesEntry}, + DeserializeDyn, DynEntry, + }, + DeserializeError, Deserializer, + }; + + fn get_vtable() -> usize { + unsafe { + core::mem::transmute(ptr_meta::metadata(core::ptr::null::>() + as *const as ArchiveUnsized>::Archived)) + } + } + inventory::submit! { DynEntry::new(*__DYN_ID_DOG_ANIMAL, get_vtable()) } + inventory::submit! { CheckBytesEntry::new(get_vtable(), default_check_bytes_dyn::>) } + + impl DeserializeDyn> for ArchivedDog + where + ArchivedDog: Deserialize, + { + fn deserialize_dyn( + &self, + deserializer: &mut Deserializer, + out: *mut dyn Animal<&'static str>, + ) -> Result<(), DeserializeError> { + unsafe { + >::deserialize_unsized(self, deserializer, out.cast()) + } + } + + fn deserialized_pointer_metadata(&self) -> ptr_meta::DynMetadata> { + ptr_meta::metadata(core::ptr::null::() as *const dyn Animal<&'static str>) + } + } + }; + + #[cacheable] + struct Cat { + color: String, + } + + static __DYN_ID_CAT_ANIMAL: std::sync::LazyLock = std::sync::LazyLock::new(|| { + use std::hash::{DefaultHasher, Hash, Hasher}; + let mut hasher = DefaultHasher::new(); + module_path!().hash(&mut hasher); + line!().hash(&mut hasher); + hasher.finish() + }); + + impl Animal for Cat { + fn color(&self) -> &str { + &self.color + } + fn name(&self) -> String { + String::from("cat") + } + fn __dyn_id(&self) -> u64 { + *__DYN_ID_CAT_ANIMAL + } + } + + const _: () = { + use rspack_cacheable::__private::{ + inventory, + rkyv::{ptr_meta, ArchiveUnsized, Archived, Deserialize, DeserializeUnsized}, + }; + use rspack_cacheable::{ + r#dyn::{ + validation::{default_check_bytes_dyn, CheckBytesEntry}, + DeserializeDyn, DynEntry, + }, + DeserializeError, Deserializer, + }; + + fn get_vtable() -> usize { + unsafe { + core::mem::transmute(ptr_meta::metadata(core::ptr::null::>() + as *const as ArchiveUnsized>::Archived)) + } + } + inventory::submit! { DynEntry::new(*__DYN_ID_CAT_ANIMAL, get_vtable()) } + inventory::submit! { CheckBytesEntry::new(get_vtable(), default_check_bytes_dyn::>)} + + impl DeserializeDyn> for ArchivedCat + where + ArchivedCat: Deserialize, + { + fn deserialize_dyn( + &self, + deserializer: &mut Deserializer, + out: *mut dyn Animal, + ) -> Result<(), DeserializeError> { + unsafe { + >::deserialize_unsized(self, deserializer, out.cast()) + } + } + + fn deserialized_pointer_metadata(&self) -> ptr_meta::DynMetadata> { + ptr_meta::metadata(core::ptr::null::() as *const dyn Animal) + } + } + }; + + #[cacheable] + struct Data { + animal_1: Box>, + animal_2: Box>, + } + + let data = Data { + animal_1: Box::new(Dog { + color: String::from("black"), + }), + animal_2: Box::new(Cat { + color: String::from("white"), + }), + }; + assert_eq!(data.animal_1.name(), "dog"); + assert_eq!(data.animal_1.color(), "black"); + assert_eq!(data.animal_2.name(), "cat"); + assert_eq!(data.animal_2.color(), "white"); + let ctx = Context {}; + let bytes = to_bytes(&data, &ctx).unwrap(); + let deserialize_data = from_bytes::(&bytes, &ctx).unwrap(); + assert_eq!(deserialize_data.animal_1.name(), "dog"); + assert_eq!(deserialize_data.animal_1.color(), "black"); + assert_eq!(deserialize_data.animal_2.name(), "cat"); + assert_eq!(deserialize_data.animal_2.color(), "white"); +} diff --git a/crates/rspack_cacheable_test/tests/macro/mod.rs b/crates/rspack_cacheable_test/tests/macro/mod.rs new file mode 100644 index 00000000000..8400b8c80c9 --- /dev/null +++ b/crates/rspack_cacheable_test/tests/macro/mod.rs @@ -0,0 +1,5 @@ +mod cacheable; +mod cacheable_dyn; +mod manual_cacheable; +mod manual_cacheable_dyn; +mod manual_cacheable_dyn_with_generics; diff --git a/crates/rspack_cacheable_test/tests/macro_case.rs b/crates/rspack_cacheable_test/tests/macro_case.rs new file mode 100644 index 00000000000..eb6455bda32 --- /dev/null +++ b/crates/rspack_cacheable_test/tests/macro_case.rs @@ -0,0 +1 @@ +mod r#macro; diff --git a/crates/rspack_cacheable_test/tests/with/as.rs b/crates/rspack_cacheable_test/tests/with/as.rs new file mode 100644 index 00000000000..399e9927677 --- /dev/null +++ b/crates/rspack_cacheable_test/tests/with/as.rs @@ -0,0 +1,40 @@ +use std::{any::Any, path::PathBuf}; + +use rspack_cacheable::{ + cacheable, + with::{As, AsConverter}, + DeserializeError, SerializeError, +}; + +#[derive(Debug, PartialEq, Eq)] +struct UnCacheableData(PathBuf); + +#[cacheable] +struct CacheableData(String); + +impl AsConverter for CacheableData { + fn serialize(data: &UnCacheableData, _ctx: &dyn Any) -> Result { + Ok(Self(data.0.to_string_lossy().to_string())) + } + fn deserialize(self, _ctx: &dyn Any) -> Result { + Ok(UnCacheableData(PathBuf::from(&self.0))) + } +} + +#[cacheable] +#[derive(Debug, PartialEq, Eq)] +struct Data { + #[cacheable(with=As)] + inner: UnCacheableData, +} + +#[test] +fn test_as() { + let data = Data { + inner: UnCacheableData(PathBuf::from("/home/user")), + }; + + let bytes = rspack_cacheable::to_bytes(&data, &()).unwrap(); + let new_data: Data = rspack_cacheable::from_bytes(&bytes, &()).unwrap(); + assert_eq!(data, new_data); +} diff --git a/crates/rspack_cacheable_test/tests/with/as_cacheable.rs b/crates/rspack_cacheable_test/tests/with/as_cacheable.rs new file mode 100644 index 00000000000..735c3d8e90c --- /dev/null +++ b/crates/rspack_cacheable_test/tests/with/as_cacheable.rs @@ -0,0 +1,25 @@ +use rspack_cacheable::{cacheable, with::AsCacheable}; + +#[cacheable] +#[derive(Debug, PartialEq, Eq)] +struct Param(String); + +#[cacheable] +#[derive(Debug, PartialEq, Eq)] +struct Data { + #[cacheable(with=AsCacheable)] + param1: Param, + param2: Param, +} + +#[test] +fn test_as_cacheable() { + let data = Data { + param1: Param(String::from("param1")), + param2: Param(String::from("param2")), + }; + + let bytes = rspack_cacheable::to_bytes(&data, &()).unwrap(); + let new_data: Data = rspack_cacheable::from_bytes(&bytes, &()).unwrap(); + assert_eq!(data, new_data); +} diff --git a/crates/rspack_cacheable_test/tests/with/as_inner.rs b/crates/rspack_cacheable_test/tests/with/as_inner.rs new file mode 100644 index 00000000000..54fd30986c9 --- /dev/null +++ b/crates/rspack_cacheable_test/tests/with/as_inner.rs @@ -0,0 +1,30 @@ +use std::{path::PathBuf, sync::Arc}; + +use rspack_cacheable::{ + cacheable, + with::{AsInner, AsString}, +}; + +#[cacheable] +#[derive(Debug, PartialEq, Eq)] +struct Data { + #[cacheable(with=AsInner)] + block1: once_cell::sync::OnceCell, + #[cacheable(with=AsInner)] + block2: once_cell::sync::OnceCell, + #[cacheable(with=AsInner)] + block3: Arc, +} + +#[test] +fn test_as_inner() { + let data = Data { + block1: once_cell::sync::OnceCell::with_value(PathBuf::from("/abc")), + block2: once_cell::sync::OnceCell::with_value(1), + block3: Arc::new(2), + }; + + let bytes = rspack_cacheable::to_bytes(&data, &()).unwrap(); + let new_data: Data = rspack_cacheable::from_bytes(&bytes, &()).unwrap(); + assert_eq!(data, new_data); +} diff --git a/crates/rspack_cacheable_test/tests/with/as_map.rs b/crates/rspack_cacheable_test/tests/with/as_map.rs new file mode 100644 index 00000000000..9f06c6791a8 --- /dev/null +++ b/crates/rspack_cacheable_test/tests/with/as_map.rs @@ -0,0 +1,60 @@ +use std::path::PathBuf; + +use dashmap::DashMap; +use rspack_cacheable::{ + cacheable, + with::{AsCacheable, AsMap, AsString}, +}; +use rustc_hash::FxHashMap; + +#[cacheable(hashable)] +#[derive(Debug, PartialEq, Eq, Hash)] +struct ModuleId(String); + +#[cacheable] +#[derive(Debug, PartialEq, Eq)] +struct Module { + content: String, +} + +#[cacheable] +#[derive(Debug)] +struct App { + modules: FxHashMap, + #[cacheable(with=AsMap)] + paths: DashMap, +} + +#[test] +fn test_as_map() { + let modules = FxHashMap::from_iter(vec![ + ( + ModuleId(String::from("file1")), + Module { + content: String::from("console.log('file')"), + }, + ), + ( + ModuleId(String::from("file2")), + Module { + content: String::from("export const file2 = 1;"), + }, + ), + ]); + let paths = dashmap::DashMap::from_iter(vec![ + (String::from("file1"), PathBuf::from("/a")), + (String::from("file2"), PathBuf::from("/a")), + ]); + let app = App { modules, paths }; + + let bytes = rspack_cacheable::to_bytes(&app, &()).unwrap(); + let new_app: App = rspack_cacheable::from_bytes(&bytes, &()).unwrap(); + assert_eq!(app.modules, new_app.modules); + assert_eq!(app.paths.len(), new_app.paths.len()); + for item in app.paths.iter() { + assert_eq!( + *item.value(), + *new_app.paths.get(item.key()).expect("should have app path") + ); + } +} diff --git a/crates/rspack_cacheable_test/tests/with/as_preset/camino.rs b/crates/rspack_cacheable_test/tests/with/as_preset/camino.rs new file mode 100644 index 00000000000..09f17969a3a --- /dev/null +++ b/crates/rspack_cacheable_test/tests/with/as_preset/camino.rs @@ -0,0 +1,20 @@ +use camino::Utf8PathBuf; +use rspack_cacheable::{cacheable, from_bytes, to_bytes, with::AsPreset}; + +#[cacheable] +#[derive(Debug, PartialEq, Eq)] +struct Module { + #[cacheable(with=AsPreset)] + path: Utf8PathBuf, +} + +#[test] +fn test_preset_camino() { + let module = Module { + path: Utf8PathBuf::from("/home/user"), + }; + + let bytes = to_bytes(&module, &()).unwrap(); + let new_module: Module = from_bytes(&bytes, &()).unwrap(); + assert_eq!(module, new_module); +} diff --git a/crates/rspack_cacheable_test/tests/with/as_preset/json.rs b/crates/rspack_cacheable_test/tests/with/as_preset/json.rs new file mode 100644 index 00000000000..b27d7c6c290 --- /dev/null +++ b/crates/rspack_cacheable_test/tests/with/as_preset/json.rs @@ -0,0 +1,20 @@ +use json::JsonValue; +use rspack_cacheable::{cacheable, from_bytes, to_bytes, with::AsPreset}; + +#[cacheable] +#[derive(Debug, PartialEq, Eq)] +struct Module { + #[cacheable(with=AsPreset)] + options: JsonValue, +} + +#[test] +fn test_preset_json() { + let module = Module { + options: json::parse("{\"id\":1}").unwrap(), + }; + + let bytes = to_bytes(&module, &()).unwrap(); + let new_module: Module = from_bytes(&bytes, &()).unwrap(); + assert_eq!(module, new_module); +} diff --git a/crates/rspack_cacheable_test/tests/with/as_preset/lightningcss.rs b/crates/rspack_cacheable_test/tests/with/as_preset/lightningcss.rs new file mode 100644 index 00000000000..9247932ec09 --- /dev/null +++ b/crates/rspack_cacheable_test/tests/with/as_preset/lightningcss.rs @@ -0,0 +1,22 @@ +use lightningcss::targets::Browsers; +use rspack_cacheable::{cacheable, from_bytes, to_bytes, with::AsPreset}; + +#[cacheable] +struct Config { + #[cacheable(with=AsPreset)] + browsers: Browsers, +} + +#[test] +fn test_preset_lightningcss() { + let config = Config { + browsers: Browsers { + chrome: Some(222), + ..Default::default() + }, + }; + + let bytes = to_bytes(&config, &()).unwrap(); + let new_config: Config = from_bytes(&bytes, &()).unwrap(); + assert_eq!(config.browsers.chrome, new_config.browsers.chrome); +} diff --git a/crates/rspack_cacheable_test/tests/with/as_preset/mod.rs b/crates/rspack_cacheable_test/tests/with/as_preset/mod.rs new file mode 100644 index 00000000000..4fa36e17091 --- /dev/null +++ b/crates/rspack_cacheable_test/tests/with/as_preset/mod.rs @@ -0,0 +1,7 @@ +mod camino; +mod json; +mod lightningcss; +mod rspack_resolver; +mod serde_json; +mod swc; +mod ustr; diff --git a/crates/rspack_cacheable_test/tests/with/as_preset/rspack_resolver.rs b/crates/rspack_cacheable_test/tests/with/as_preset/rspack_resolver.rs new file mode 100644 index 00000000000..2f776483078 --- /dev/null +++ b/crates/rspack_cacheable_test/tests/with/as_preset/rspack_resolver.rs @@ -0,0 +1,37 @@ +use rspack_cacheable::{ + cacheable, from_bytes, to_bytes, + with::{AsCacheable, AsOption, AsPreset, AsTuple2, AsVec}, +}; +use rspack_resolver::{Alias, AliasValue}; + +#[cacheable] +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +struct ResolverOption { + #[cacheable(with=AsOption>>>)] + alias: Option, +} + +#[test] +fn test_preset_rspack_resolver() { + let option = ResolverOption { + alias: Some(vec![ + ( + String::from("@"), + vec![AliasValue::Path(String::from("./src"))], + ), + (String::from("ignore"), vec![AliasValue::Ignore]), + ( + String::from("components"), + vec![ + AliasValue::Path(String::from("./components")), + AliasValue::Path(String::from("./src")), + AliasValue::Ignore, + ], + ), + ]), + }; + + let bytes = to_bytes(&option, &()).unwrap(); + let new_option: ResolverOption = from_bytes(&bytes, &()).unwrap(); + assert_eq!(option, new_option); +} diff --git a/crates/rspack_cacheable_test/tests/with/as_preset/serde_json.rs b/crates/rspack_cacheable_test/tests/with/as_preset/serde_json.rs new file mode 100644 index 00000000000..aa43c470ded --- /dev/null +++ b/crates/rspack_cacheable_test/tests/with/as_preset/serde_json.rs @@ -0,0 +1,20 @@ +use rspack_cacheable::{cacheable, from_bytes, to_bytes, with::AsPreset}; +use serde_json::Value; + +#[cacheable] +#[derive(Debug, PartialEq, Eq)] +struct Module { + #[cacheable(with=AsPreset)] + options: Value, +} + +#[test] +fn test_preset_serde_json() { + let module = Module { + options: serde_json::from_str("{\"id\":1}").unwrap(), + }; + + let bytes = to_bytes(&module, &()).unwrap(); + let new_module: Module = from_bytes(&bytes, &()).unwrap(); + assert_eq!(module, new_module); +} diff --git a/crates/rspack_cacheable_test/tests/with/as_preset/swc.rs b/crates/rspack_cacheable_test/tests/with/as_preset/swc.rs new file mode 100644 index 00000000000..93afd1d5a1c --- /dev/null +++ b/crates/rspack_cacheable_test/tests/with/as_preset/swc.rs @@ -0,0 +1,20 @@ +use rspack_cacheable::{cacheable, from_bytes, to_bytes, with::AsPreset}; +use swc_core::ecma::atoms::Atom; + +#[cacheable] +#[derive(Debug, PartialEq, Eq)] +struct Module { + #[cacheable(with=AsPreset)] + id: Atom, +} + +#[test] +fn test_preset_swc() { + let module = Module { + id: Atom::new("abc"), + }; + + let bytes = to_bytes(&module, &()).unwrap(); + let new_module: Module = from_bytes(&bytes, &()).unwrap(); + assert_eq!(module, new_module); +} diff --git a/crates/rspack_cacheable_test/tests/with/as_preset/ustr.rs b/crates/rspack_cacheable_test/tests/with/as_preset/ustr.rs new file mode 100644 index 00000000000..5548ba5fb55 --- /dev/null +++ b/crates/rspack_cacheable_test/tests/with/as_preset/ustr.rs @@ -0,0 +1,20 @@ +use rspack_cacheable::{cacheable, from_bytes, to_bytes, with::AsPreset}; +use ustr::Ustr; + +#[cacheable] +#[derive(Debug, PartialEq, Eq)] +struct Module { + #[cacheable(with=AsPreset)] + id: Ustr, +} + +#[test] +fn test_preset_ustr() { + let module = Module { + id: Ustr::from("abc"), + }; + + let bytes = to_bytes(&module, &()).unwrap(); + let new_module: Module = from_bytes(&bytes, &()).unwrap(); + assert_eq!(module, new_module); +} diff --git a/crates/rspack_cacheable_test/tests/with/as_ref_str.rs b/crates/rspack_cacheable_test/tests/with/as_ref_str.rs new file mode 100644 index 00000000000..781e828405b --- /dev/null +++ b/crates/rspack_cacheable_test/tests/with/as_ref_str.rs @@ -0,0 +1,44 @@ +use rspack_cacheable::{ + cacheable, + with::{AsRefStr, AsRefStrConverter}, +}; + +#[derive(Debug, PartialEq, Eq)] +struct UnCacheable; + +#[derive(Debug, PartialEq, Eq)] +struct Cat { + name: String, + uncacheable: UnCacheable, +} +impl AsRefStrConverter for Cat { + fn as_str(&self) -> &str { + &self.name + } + fn from_str(s: &str) -> Self { + Self { + name: String::from(s), + uncacheable: UnCacheable, + } + } +} + +#[cacheable] +#[derive(Debug, PartialEq, Eq)] +struct Home { + #[cacheable(with=AsRefStr)] + cat: Cat, +} + +#[test] +fn test_as_ref_string() { + let home = Home { + cat: Cat { + name: String::from("a"), + uncacheable: UnCacheable, + }, + }; + let bytes = rspack_cacheable::to_bytes(&home, &()).unwrap(); + let new_home: Home = rspack_cacheable::from_bytes(&bytes, &()).unwrap(); + assert_eq!(home, new_home); +} diff --git a/crates/rspack_cacheable_test/tests/with/as_string.rs b/crates/rspack_cacheable_test/tests/with/as_string.rs new file mode 100644 index 00000000000..1f268a28956 --- /dev/null +++ b/crates/rspack_cacheable_test/tests/with/as_string.rs @@ -0,0 +1,53 @@ +use std::path::PathBuf; + +use rspack_cacheable::{ + cacheable, + with::{AsString, AsStringConverter}, + DeserializeError, SerializeError, +}; + +#[cacheable(with=AsString)] +#[derive(Debug, PartialEq, Eq)] +struct Regex { + source: String, + flags: String, +} +impl AsStringConverter for Regex { + fn to_string(&self) -> Result { + Ok(format!("{}#{}", self.flags, self.source)) + } + fn from_str(s: &str) -> Result + where + Self: Sized, + { + let (flags, source) = s.split_once('#').expect("should have flags"); + Ok(Self { + source: String::from(source), + flags: String::from(flags), + }) + } +} + +#[cacheable] +#[derive(Debug, PartialEq, Eq)] +struct Module { + #[cacheable(with=AsString)] + path: PathBuf, + #[cacheable(with=AsString)] + regex: Regex, +} + +#[test] +fn test_as_string() { + let module = Module { + path: PathBuf::from("/root"), + regex: Regex { + source: String::from("/.*/"), + flags: String::from("g"), + }, + }; + + let bytes = rspack_cacheable::to_bytes(&module, &()).unwrap(); + let new_module: Module = rspack_cacheable::from_bytes(&bytes, &()).unwrap(); + assert_eq!(module, new_module); +} diff --git a/crates/rspack_cacheable_test/tests/with/as_tuple2.rs b/crates/rspack_cacheable_test/tests/with/as_tuple2.rs new file mode 100644 index 00000000000..cf90347816a --- /dev/null +++ b/crates/rspack_cacheable_test/tests/with/as_tuple2.rs @@ -0,0 +1,29 @@ +use std::path::PathBuf; + +use rspack_cacheable::{ + cacheable, + with::{AsCacheable, AsString, AsTuple2}, +}; + +#[cacheable] +#[derive(Debug, PartialEq, Eq)] +struct Data { + param1: (u32, u32), + #[cacheable(with=AsTuple2)] + param2: (u32, u32), + #[cacheable(with=AsTuple2)] + param3: (u32, PathBuf), +} + +#[test] +fn test_as_tuple2() { + let data = Data { + param1: (1, 2), + param2: (3, 4), + param3: (5, PathBuf::from("/root")), + }; + + let bytes = rspack_cacheable::to_bytes(&data, &()).unwrap(); + let new_data: Data = rspack_cacheable::from_bytes(&bytes, &()).unwrap(); + assert_eq!(data, new_data); +} diff --git a/crates/rspack_cacheable_test/tests/with/as_tuple3.rs b/crates/rspack_cacheable_test/tests/with/as_tuple3.rs new file mode 100644 index 00000000000..96a7d8abd66 --- /dev/null +++ b/crates/rspack_cacheable_test/tests/with/as_tuple3.rs @@ -0,0 +1,29 @@ +use std::path::PathBuf; + +use rspack_cacheable::{ + cacheable, + with::{AsCacheable, AsString, AsTuple3}, +}; + +#[cacheable] +#[derive(Debug, PartialEq, Eq)] +struct Data { + param1: (u32, u32, u32), + #[cacheable(with=AsTuple3)] + param2: (u32, u32, u32), + #[cacheable(with=AsTuple3)] + param3: (u32, u32, PathBuf), +} + +#[test] +fn test_as_tuple3() { + let data = Data { + param1: (1, 2, 3), + param2: (4, 5, 6), + param3: (7, 8, PathBuf::from("/root")), + }; + + let bytes = rspack_cacheable::to_bytes(&data, &()).unwrap(); + let new_data: Data = rspack_cacheable::from_bytes(&bytes, &()).unwrap(); + assert_eq!(data, new_data); +} diff --git a/crates/rspack_cacheable_test/tests/with/as_vec.rs b/crates/rspack_cacheable_test/tests/with/as_vec.rs new file mode 100644 index 00000000000..85e17a50c67 --- /dev/null +++ b/crates/rspack_cacheable_test/tests/with/as_vec.rs @@ -0,0 +1,46 @@ +use std::path::PathBuf; + +use rspack_cacheable::{ + cacheable, + with::{AsString, AsVec}, +}; +use rustc_hash::FxHashSet; + +#[cacheable(hashable)] +#[derive(Debug, PartialEq, Eq, Hash)] +struct Module { + name: String, +} + +#[cacheable] +#[derive(Debug, PartialEq, Eq)] +struct App { + modules: FxHashSet, + #[cacheable(with=AsVec)] + paths: Vec, + sizes: Vec, +} + +#[test] +fn test_as_vec() { + let modules = FxHashSet::from_iter(vec![ + Module { + name: String::from("a"), + }, + Module { + name: String::from("b"), + }, + ]); + let paths = vec![PathBuf::from("/a"), PathBuf::from("/b")]; + let sizes = vec![1, 2]; + + let app = App { + modules, + paths, + sizes, + }; + + let bytes = rspack_cacheable::to_bytes(&app, &()).unwrap(); + let new_app: App = rspack_cacheable::from_bytes(&bytes, &()).unwrap(); + assert_eq!(app, new_app); +} diff --git a/crates/rspack_cacheable_test/tests/with/mod.rs b/crates/rspack_cacheable_test/tests/with/mod.rs new file mode 100644 index 00000000000..29cca03fe9e --- /dev/null +++ b/crates/rspack_cacheable_test/tests/with/mod.rs @@ -0,0 +1,11 @@ +mod r#as; +mod as_cacheable; +mod as_inner; +mod as_map; +mod as_preset; +mod as_ref_str; +mod as_string; +mod as_tuple2; +mod as_tuple3; +mod as_vec; +mod unsupported; diff --git a/crates/rspack_cacheable_test/tests/with/unsupported.rs b/crates/rspack_cacheable_test/tests/with/unsupported.rs new file mode 100644 index 00000000000..ffe2aca19d7 --- /dev/null +++ b/crates/rspack_cacheable_test/tests/with/unsupported.rs @@ -0,0 +1,19 @@ +use rspack_cacheable::{cacheable, with::Unsupported, SerializeError}; + +struct UnCacheable; + +#[cacheable] +struct App { + #[cacheable(with=Unsupported)] + info: UnCacheable, +} + +#[test] +fn test_unsupport() { + let app = App { info: UnCacheable }; + + assert!(matches!( + rspack_cacheable::to_bytes(&app, &()), + Err(SerializeError::UnsupportedField) + )); +} diff --git a/crates/rspack_cacheable_test/tests/with_case.rs b/crates/rspack_cacheable_test/tests/with_case.rs new file mode 100644 index 00000000000..3b18bbdfb63 --- /dev/null +++ b/crates/rspack_cacheable_test/tests/with_case.rs @@ -0,0 +1 @@ +mod with; diff --git a/crates/rspack_macros/src/cacheable.rs b/crates/rspack_macros/src/cacheable.rs new file mode 100644 index 00000000000..c5e22eabd8a --- /dev/null +++ b/crates/rspack_macros/src/cacheable.rs @@ -0,0 +1,241 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{ + parse::{Parse, ParseStream}, + parse_macro_input, parse_quote, + visit_mut::VisitMut, + Field, GenericParam, Item, Result, Token, Type, +}; + +mod kw { + syn::custom_keyword!(with); + syn::custom_keyword!(hashable); +} + +/// #[cacheable] type-only args +pub struct CacheableArgs { + pub crate_path: syn::Path, + pub with: Option, + pub hashable: bool, +} +impl Parse for CacheableArgs { + fn parse(input: ParseStream) -> Result { + let mut with = None; + let mut crate_path = parse_quote! { ::rspack_cacheable }; + let mut hashable = false; + + let mut needs_punct = false; + while !input.is_empty() { + if needs_punct { + input.parse::()?; + } + + if input.peek(syn::token::Crate) { + input.parse::()?; + input.parse::()?; + crate_path = input.parse::()?; + } else if input.peek(kw::with) { + if with.is_some() { + return Err(input.error("duplicate with argument")); + } + + input.parse::()?; + input.parse::()?; + with = Some(input.parse::()?); + } else if input.peek(kw::hashable) { + input.parse::()?; + hashable = true; + } else { + return Err(input.error("unexpected #[cacheable] type-only parameters")); + } + + needs_punct = true; + } + + Ok(Self { + crate_path, + with, + hashable, + }) + } +} + +/// A visitor to transform #[cacheable] on field +#[derive(Default)] +struct FieldAttrVisitor { + /// Remove all #[cacheable] attr on field + clean: bool, + /// Whether any field set #[cacheable(omit_bounds)] + omit_bounds: bool, +} + +impl VisitMut for FieldAttrVisitor { + fn visit_field_mut(&mut self, f: &mut Field) { + let mut with_info = None; + let mut omit_bounds = false; + f.attrs.retain(|item| { + if item.path().is_ident("cacheable") { + let _ = item.parse_nested_meta(|meta| { + if meta.path.is_ident("with") { + meta.input.parse::()?; + with_info = Some(meta.input.parse::()?); + return Ok(()); + } + if meta.path.is_ident("omit_bounds") { + omit_bounds = true; + return Ok(()); + } + Err(meta.error("unrecognized cacheable arguments")) + }); + false + } else { + true + } + }); + + // enable clean, just remove all cacheable attributes + if self.clean { + return; + } + // add rkyv with + if let Some(with_info) = with_info { + f.attrs.push(parse_quote!(#[rkyv(with=#with_info)])); + } + // add rkyv omit_bounds + if omit_bounds { + self.omit_bounds = true; + f.attrs.push(parse_quote!(#[rkyv(omit_bounds)])); + } + } +} + +/// impl #[cacheable] without with args +pub fn impl_cacheable(tokens: TokenStream, args: CacheableArgs) -> TokenStream { + let mut input = parse_macro_input!(tokens as Item); + + let mut visitor = FieldAttrVisitor::default(); + visitor.visit_item_mut(&mut input); + + let crate_path = &args.crate_path; + let archived_impl_hash = if args.hashable { + quote! {#[rkyv(derive(Hash, PartialEq, Eq))]} + } else { + quote! {} + }; + let bounds = if visitor.omit_bounds { + quote! { + #[rkyv(serialize_bounds( + __S: #crate_path::__private::rkyv::ser::Writer + #crate_path::__private::rkyv::ser::Allocator + #crate_path::__private::rkyv::rancor::Fallible, + ))] + #[rkyv(deserialize_bounds( + __D: #crate_path::__private::rkyv::rancor::Fallible + ))] + #[rkyv(bytecheck( + bounds( + __C: #crate_path::__private::rkyv::validation::ArchiveContext + #crate_path::__private::rkyv::rancor::Fallible, + ) + ))] + } + } else { + quote! {} + }; + + quote! { + #[derive( + #crate_path::__private::rkyv::Archive, + #crate_path::__private::rkyv::Deserialize, + #crate_path::__private::rkyv::Serialize + )] + #[rkyv(crate=#crate_path::__private::rkyv)] + #archived_impl_hash + #bounds + #input + } + .into() +} + +/// impl #[cacheable] with `with` args +pub fn impl_cacheable_with(tokens: TokenStream, args: CacheableArgs) -> TokenStream { + let mut input = parse_macro_input!(tokens as Item); + + let mut visitor = FieldAttrVisitor { + clean: true, + ..Default::default() + }; + visitor.visit_item_mut(&mut input); + + let crate_path = args.crate_path; + let with = args.with; + + let (ident, generics) = match &input { + Item::Enum(input) => (&input.ident, &input.generics), + Item::Struct(input) => (&input.ident, &input.generics), + _ => panic!("expect enum or struct"), + }; + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + let generic_params = generics.params.iter().map(|p| { + // remove default value + let mut p = p.clone(); + if let GenericParam::Type(param) = &mut p { + param.eq_token = None; + param.default = None; + } + quote! { #p } + }); + let generic_params = quote! { #(#generic_params),* }; + + let where_params = if let Some(where_clause) = &generics.where_clause { + let params = where_clause.predicates.iter().map(|w| { + quote! { #w } + }); + quote! { #(#params),* } + } else { + quote! {} + }; + + let archived = quote! {<#with as rkyv::with::ArchiveWith<#ident #ty_generics>>::Archived}; + let resolver = quote! {<#with as rkyv::with::ArchiveWith<#ident #ty_generics>>::Resolver}; + quote! { + #input + #[allow(non_upper_case_globals)] + const _: () = { + use #crate_path::__private::rkyv; + use rkyv::{ + rancor::Fallible, + with::{ArchiveWith, DeserializeWith, SerializeWith}, + Archive, Deserialize, Place, Serialize + }; + impl #impl_generics Archive for #ident #ty_generics #where_clause { + type Archived = #archived; + type Resolver = #resolver; + #[inline] + fn resolve(&self, resolver: Self::Resolver, out: Place) { + <#with as ArchiveWith<#ident #ty_generics>>::resolve_with(self, resolver, out) + } + } + impl<__S, #generic_params> Serialize<__S> for #ident #ty_generics + where + __S: Fallible + ?Sized, + #with: SerializeWith<#ident #ty_generics, __S>, + #where_params + { + #[inline] + fn serialize(&self, serializer: &mut __S) -> Result { + #with::serialize_with(self, serializer) + } + } + impl<__D, #generic_params> Deserialize<#ident #ty_generics, __D> for #archived + where + __D: Fallible + ?Sized, + #with: DeserializeWith<#archived, #ident #ty_generics, __D>, + #where_params + { + #[inline] + fn deserialize(&self, deserializer: &mut __D) -> Result<#ident #ty_generics, __D::Error> { + #with::deserialize_with(self, deserializer) + } + } + }; + } + .into() +} diff --git a/crates/rspack_macros/src/cacheable_dyn.rs b/crates/rspack_macros/src/cacheable_dyn.rs new file mode 100644 index 00000000000..2ae938d33fa --- /dev/null +++ b/crates/rspack_macros/src/cacheable_dyn.rs @@ -0,0 +1,234 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_quote, spanned::Spanned, GenericParam, Ident, ItemImpl, ItemTrait, Type}; + +/// For trait definition: `pub trait Iterator { ... }` +pub fn impl_trait(mut input: ItemTrait) -> TokenStream { + let trait_ident = &input.ident; + let trait_vis = &input.vis; + let generic_params = input.generics.params.iter().map(|p| { + // remove default value + let mut p = p.clone(); + if let GenericParam::Type(param) = &mut p { + param.eq_token = None; + param.default = None; + } + quote! { #p } + }); + let generic_params = quote! { #(#generic_params),* }; + let deserialize_trait_ident = + Ident::new(&format!("Deserialize{trait_ident}"), trait_ident.span()); + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + + input + .supertraits + .push(parse_quote! { rspack_cacheable::r#dyn::SerializeDyn }); + + input.items.push(parse_quote! { + #[doc(hidden)] + fn __dyn_id(&self) -> u64; + }); + + quote! { + #input + + const _: () = { + use std::alloc::{Layout, LayoutError}; + + use rspack_cacheable::__private::rkyv::{ + bytecheck::CheckBytes, + ptr_meta, + traits::{ArchivePointee, LayoutRaw}, + ArchiveUnsized, ArchivedMetadata, DeserializeUnsized, Portable, SerializeUnsized, + }; + use rspack_cacheable::{ + r#dyn::{validation::CHECK_BYTES_REGISTRY, ArchivedDynMetadata, DeserializeDyn}, + DeserializeError, Deserializer, SerializeError, Serializer, Validator, + }; + + unsafe impl #impl_generics ptr_meta::Pointee for dyn #trait_ident #ty_generics #where_clause { + type Metadata = ptr_meta::DynMetadata; + } + + impl #ty_generics ArchiveUnsized for dyn #trait_ident #ty_generics #where_clause { + type Archived = dyn #deserialize_trait_ident #ty_generics; + + fn archived_metadata(&self) -> ArchivedMetadata { + ArchivedDynMetadata::new(#trait_ident::__dyn_id(self)) + } + } + + impl #ty_generics LayoutRaw for dyn #trait_ident #ty_generics #where_clause { + fn layout_raw( + metadata: ::Metadata, + ) -> Result { + Ok(metadata.layout()) + } + } + + impl #ty_generics SerializeUnsized> for dyn #trait_ident #ty_generics #where_clause { + fn serialize_unsized( + &self, + serializer: &mut Serializer + ) -> Result { + self.serialize_dyn(serializer) + } + } + + #trait_vis trait #deserialize_trait_ident #ty_generics: DeserializeDyn + Portable #where_clause {} + unsafe impl #ty_generics ptr_meta::Pointee for dyn #deserialize_trait_ident #ty_generics #where_clause { + type Metadata = ptr_meta::DynMetadata; + } + + impl<__T: DeserializeDyn + Portable, #generic_params> #deserialize_trait_ident #ty_generics for __T #where_clause {} + + impl #ty_generics ArchivePointee for dyn #deserialize_trait_ident #ty_generics #where_clause { + type ArchivedMetadata = ArchivedDynMetadata; + + fn pointer_metadata( + archived: &Self::ArchivedMetadata, + ) -> ::Metadata { + archived.lookup_metadata() + } + } + + + impl #ty_generics DeserializeUnsized for dyn #deserialize_trait_ident #ty_generics #where_clause { + unsafe fn deserialize_unsized( + &self, + deserializer: &mut Deserializer, + out: *mut dyn #trait_ident #ty_generics + ) -> Result<(), DeserializeError> { + self.deserialize_dyn(deserializer, out) + } + + fn deserialize_metadata(&self) -> ::Metadata { + self.deserialized_pointer_metadata() + } + } + + impl #ty_generics LayoutRaw for dyn #deserialize_trait_ident #ty_generics #where_clause { + fn layout_raw( + metadata: ::Metadata, + ) -> Result { + Ok(metadata.layout()) + } + } + + // CheckBytes + unsafe impl #ty_generics CheckBytes> for dyn #deserialize_trait_ident #ty_generics #where_clause { + #[inline] + unsafe fn check_bytes( + value: *const Self, + context: &mut Validator, + ) -> Result<(), DeserializeError> { + let vtable: usize = std::mem::transmute(ptr_meta::metadata(value)); + if let Some(check_bytes_dyn) = CHECK_BYTES_REGISTRY.get(&vtable) { + check_bytes_dyn(value.cast(), context)?; + Ok(()) + } else { + Err(DeserializeError::DynCheckBytesNotRegister) + } + } + } + }; + } + .into() +} + +/// For impl block providing trait or associated items: `impl Trait +/// for Data { ... }`. +pub fn impl_impl(mut input: ItemImpl) -> TokenStream { + let trait_ident = &input.trait_.as_ref().expect("should have trait ident").1; + let trait_ident_str = trait_ident + .segments + .last() + .expect("should have segments") + .ident + .to_string(); + let target_ident = &input.self_ty; + let target_ident_str = match &*input.self_ty { + Type::Path(inner) => inner + .path + .segments + .last() + .expect("should have segments") + .ident + .to_string(), + _ => { + panic!("cacheable_dyn unsupported this target") + } + }; + let archived_target_ident = Ident::new(&format!("Archived{}", target_ident_str), input.span()); + #[allow(clippy::disallowed_methods)] + let dyn_id_ident = Ident::new( + &format!( + "__DYN_ID_{}_{}", + target_ident_str.to_ascii_uppercase(), + trait_ident_str.to_ascii_uppercase() + ), + input.span(), + ); + + input.items.push(parse_quote! { + fn __dyn_id(&self) -> u64 { + *#dyn_id_ident + } + }); + + quote! { + static #dyn_id_ident: std::sync::LazyLock = std::sync::LazyLock::new(|| { + use std::hash::{DefaultHasher, Hash, Hasher}; + let mut hasher = DefaultHasher::new(); + module_path!().hash(&mut hasher); + line!().hash(&mut hasher); + hasher.finish() + }); + + #input + + const _: () = { + use rspack_cacheable::__private::{ + inventory, + rkyv::{ptr_meta, ArchiveUnsized, Archived, Deserialize, DeserializeUnsized}, + }; + use rspack_cacheable::{ + r#dyn::{ + validation::{default_check_bytes_dyn, CheckBytesEntry}, + DeserializeDyn, DynEntry, + }, + DeserializeError, Deserializer, + }; + + fn get_vtable() -> usize { + unsafe { + core::mem::transmute(ptr_meta::metadata( + core::ptr::null::>() as *const ::Archived + )) + } + } + inventory::submit! { DynEntry::new(*#dyn_id_ident, get_vtable()) } + inventory::submit! { CheckBytesEntry::new(get_vtable(), default_check_bytes_dyn::>) } + + impl DeserializeDyn for #archived_target_ident + where + #archived_target_ident: Deserialize<#target_ident, Deserializer>, + { + fn deserialize_dyn( + &self, + deserializer: &mut Deserializer, + out: *mut dyn #trait_ident + ) -> Result<(), DeserializeError> { + unsafe { + >::deserialize_unsized(self, deserializer, out.cast()) + } + } + + fn deserialized_pointer_metadata(&self) -> ptr_meta::DynMetadata { + ptr_meta::metadata(core::ptr::null::<#target_ident>() as *const dyn #trait_ident) + } + } + }; + } + .into() +} diff --git a/crates/rspack_macros/src/lib.rs b/crates/rspack_macros/src/lib.rs index 8813409bd72..2d37284ebab 100644 --- a/crates/rspack_macros/src/lib.rs +++ b/crates/rspack_macros/src/lib.rs @@ -1,5 +1,7 @@ #![feature(try_find)] +mod cacheable; +mod cacheable_dyn; mod hook; mod merge; mod plugin; @@ -61,3 +63,30 @@ pub fn merge_from_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStr } .into() } + +#[proc_macro_attribute] +pub fn cacheable( + args: proc_macro::TokenStream, + tokens: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + let args = syn::parse_macro_input!(args as cacheable::CacheableArgs); + if args.with.is_some() { + cacheable::impl_cacheable_with(tokens, args) + } else { + cacheable::impl_cacheable(tokens, args) + } +} + +#[proc_macro_attribute] +pub fn cacheable_dyn( + _args: proc_macro::TokenStream, + tokens: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + let input = syn::parse_macro_input!(tokens as syn::Item); + + match input { + syn::Item::Trait(input) => cacheable_dyn::impl_trait(input), + syn::Item::Impl(input) => cacheable_dyn::impl_impl(input), + _ => panic!("expect Trait or Impl"), + } +} From 034b867a762940fb91258f082923dc0a43d4f97e Mon Sep 17 00:00:00 2001 From: jerrykingxyz Date: Tue, 22 Oct 2024 15:50:29 +0800 Subject: [PATCH 2/3] feat: add noop feature to disable macro --- crates/rspack_cacheable/Cargo.toml | 3 +++ crates/rspack_cacheable/src/lib.rs | 3 +++ crates/rspack_macros/src/cacheable.rs | 16 ++++++++++++++++ crates/rspack_macros/src/cacheable_dyn.rs | 5 +++++ crates/rspack_macros/src/lib.rs | 16 ++++++++++++++++ 5 files changed, 43 insertions(+) diff --git a/crates/rspack_cacheable/Cargo.toml b/crates/rspack_cacheable/Cargo.toml index c1550a267b5..f76b0b5f360 100644 --- a/crates/rspack_cacheable/Cargo.toml +++ b/crates/rspack_cacheable/Cargo.toml @@ -4,6 +4,9 @@ license = "MIT" name = "rspack_cacheable" version = "0.1.0" +[features] +noop = [] + [dependencies] camino = { workspace = true } dashmap = { workspace = true } diff --git a/crates/rspack_cacheable/src/lib.rs b/crates/rspack_cacheable/src/lib.rs index dea6e8e594a..aaf1941ed65 100644 --- a/crates/rspack_cacheable/src/lib.rs +++ b/crates/rspack_cacheable/src/lib.rs @@ -1,4 +1,7 @@ +#[cfg(not(feature = "noop"))] pub use rspack_macros::{cacheable, cacheable_dyn}; +#[cfg(feature = "noop")] +pub use rspack_macros::{disable_cacheable as cacheable, disable_cacheable_dyn as cacheable_dyn}; pub mod r#dyn; pub mod utils; pub mod with; diff --git a/crates/rspack_macros/src/cacheable.rs b/crates/rspack_macros/src/cacheable.rs index c5e22eabd8a..815d93954bb 100644 --- a/crates/rspack_macros/src/cacheable.rs +++ b/crates/rspack_macros/src/cacheable.rs @@ -239,3 +239,19 @@ pub fn impl_cacheable_with(tokens: TokenStream, args: CacheableArgs) -> TokenStr } .into() } + +/// impl cacheable when disable +pub fn disable_cacheable(tokens: TokenStream) -> TokenStream { + let mut input = parse_macro_input!(tokens as Item); + + let mut visitor = FieldAttrVisitor { + clean: true, + ..Default::default() + }; + visitor.visit_item_mut(&mut input); + + quote! { + #input + } + .into() +} diff --git a/crates/rspack_macros/src/cacheable_dyn.rs b/crates/rspack_macros/src/cacheable_dyn.rs index 2ae938d33fa..81765e8c365 100644 --- a/crates/rspack_macros/src/cacheable_dyn.rs +++ b/crates/rspack_macros/src/cacheable_dyn.rs @@ -232,3 +232,8 @@ pub fn impl_impl(mut input: ItemImpl) -> TokenStream { } .into() } + +/// impl cacheable dyn when disable +pub fn disable_cacheable_dyn(input: TokenStream) -> TokenStream { + input +} diff --git a/crates/rspack_macros/src/lib.rs b/crates/rspack_macros/src/lib.rs index 2d37284ebab..f0157b8918b 100644 --- a/crates/rspack_macros/src/lib.rs +++ b/crates/rspack_macros/src/lib.rs @@ -77,6 +77,14 @@ pub fn cacheable( } } +#[proc_macro_attribute] +pub fn disable_cacheable( + _args: proc_macro::TokenStream, + tokens: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + cacheable::disable_cacheable(tokens) +} + #[proc_macro_attribute] pub fn cacheable_dyn( _args: proc_macro::TokenStream, @@ -90,3 +98,11 @@ pub fn cacheable_dyn( _ => panic!("expect Trait or Impl"), } } + +#[proc_macro_attribute] +pub fn disable_cacheable_dyn( + _args: proc_macro::TokenStream, + tokens: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + cacheable_dyn::disable_cacheable_dyn(tokens) +} From e5ce8189a9abb84c175e87981b4fbb90a7c74e7a Mon Sep 17 00:00:00 2001 From: jerrykingxyz Date: Tue, 22 Oct 2024 16:06:15 +0800 Subject: [PATCH 3/3] fix: lightningcss -> lightningcss_rs --- Cargo.lock | 4 ++-- crates/rspack_cacheable/Cargo.toml | 2 +- crates/rspack_cacheable_test/Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 48affa75e66..7fee5acfe19 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3584,7 +3584,7 @@ dependencies = [ "indexmap 2.2.6", "inventory", "json", - "lightningcss", + "lightningcss_rs", "once_cell", "rkyv 0.8.8", "rspack_macros", @@ -3602,7 +3602,7 @@ dependencies = [ "camino", "dashmap 5.5.3", "json", - "lightningcss", + "lightningcss_rs", "once_cell", "rspack_cacheable", "rspack_resolver", diff --git a/crates/rspack_cacheable/Cargo.toml b/crates/rspack_cacheable/Cargo.toml index f76b0b5f360..1be3568ad88 100644 --- a/crates/rspack_cacheable/Cargo.toml +++ b/crates/rspack_cacheable/Cargo.toml @@ -14,7 +14,7 @@ hashlink = { workspace = true } indexmap = { workspace = true } inventory = { workspace = true } json = { workspace = true } -lightningcss = { workspace = true } +lightningcss_rs = { workspace = true } once_cell = { workspace = true } rkyv = { workspace = true } rspack_macros = { path = "../rspack_macros" } diff --git a/crates/rspack_cacheable_test/Cargo.toml b/crates/rspack_cacheable_test/Cargo.toml index 16ea7992afa..914601eb532 100644 --- a/crates/rspack_cacheable_test/Cargo.toml +++ b/crates/rspack_cacheable_test/Cargo.toml @@ -11,7 +11,7 @@ version = "0.1.0" camino = { workspace = true } dashmap = { workspace = true } json = { workspace = true } -lightningcss = { workspace = true } +lightningcss_rs = { workspace = true } once_cell = { workspace = true } rspack_cacheable = { path = "../rspack_cacheable" } rspack_resolver = { workspace = true }