From e0ee86ae33b049493e3f7c71eb973b01833bf618 Mon Sep 17 00:00:00 2001 From: Zibi Braniecki Date: Thu, 29 Jul 2021 21:30:25 +0000 Subject: [PATCH] Bug 1672317 - [l10nfilesource] part2: Vendor in l10nregistry-rs and fluent-fallback. r=dminor Depends on D103184 Differential Revision: https://phabricator.services.mozilla.com/D102102 --- .cargo/config.in | 5 + Cargo.lock | 111 +- intl/l10n/rust/fluent-ffi/Cargo.toml | 5 +- .../rust/async-trait/.cargo-checksum.json | 1 + third_party/rust/async-trait/Cargo.toml | 51 + third_party/rust/async-trait/LICENSE-APACHE | 201 +++ third_party/rust/async-trait/LICENSE-MIT | 23 + third_party/rust/async-trait/README.md | 262 ++++ third_party/rust/async-trait/src/args.rs | 36 + third_party/rust/async-trait/src/expand.rs | 502 ++++++++ third_party/rust/async-trait/src/lib.rs | 330 +++++ third_party/rust/async-trait/src/lifetime.rs | 80 ++ third_party/rust/async-trait/src/parse.rs | 34 + third_party/rust/async-trait/src/receiver.rs | 321 +++++ third_party/rust/async-trait/src/respan.rs | 22 + .../rust/async-trait/tests/compiletest.rs | 6 + .../rust/async-trait/tests/executor/mod.rs | 35 + third_party/rust/async-trait/tests/test.rs | 1079 +++++++++++++++++ .../async-trait/tests/ui/bare-trait-object.rs | 15 + .../tests/ui/bare-trait-object.stderr | 11 + .../async-trait/tests/ui/delimiter-span.rs | 21 + .../tests/ui/delimiter-span.stderr | 8 + .../rust/async-trait/tests/ui/missing-body.rs | 15 + .../async-trait/tests/ui/missing-body.stderr | 7 + .../rust/async-trait/tests/ui/must-use.rs | 21 + .../rust/async-trait/tests/ui/must-use.stderr | 11 + .../rust/async-trait/tests/ui/self-span.rs | 30 + .../async-trait/tests/ui/self-span.stderr | 30 + .../tests/ui/send-not-implemented.rs | 15 + .../tests/ui/send-not-implemented.stderr | 22 + .../async-trait/tests/ui/unsupported-self.rs | 15 + .../tests/ui/unsupported-self.stderr | 5 + .../rust/chunky-vec/.cargo-checksum.json | 1 + third_party/rust/chunky-vec/Cargo.toml | 24 + third_party/rust/chunky-vec/LICENSE-APACHE | 201 +++ third_party/rust/chunky-vec/LICENSE-MIT | 27 + third_party/rust/chunky-vec/README.md | 6 + third_party/rust/chunky-vec/src/lib.rs | 343 ++++++ .../rust/fluent-bundle/.cargo-checksum.json | 2 +- third_party/rust/fluent-bundle/Cargo.toml | 4 +- third_party/rust/fluent-bundle/src/lib.rs | 2 +- .../rust/fluent-bundle/src/resolver/mod.rs | 2 +- .../fluent-bundle/src/resolver/pattern.rs | 19 +- .../rust/fluent-fallback/.cargo-checksum.json | 1 + third_party/rust/fluent-fallback/CHANGELOG.md | 61 + third_party/rust/fluent-fallback/Cargo.lock | 468 +++++++ third_party/rust/fluent-fallback/Cargo.toml | 51 + .../rust/fluent-fallback/LICENSE-APACHE | 201 +++ third_party/rust/fluent-fallback/LICENSE-MIT | 19 + third_party/rust/fluent-fallback/README.md | 102 ++ .../examples/resources/en-US/simple.ftl | 7 + .../examples/resources/pl/simple.ftl | 8 + .../examples/simple-fallback.rs | 231 ++++ .../rust/fluent-fallback/src/bundles.rs | 425 +++++++ third_party/rust/fluent-fallback/src/cache.rs | 253 ++++ third_party/rust/fluent-fallback/src/env.rs | 84 ++ .../rust/fluent-fallback/src/errors.rs | 71 ++ .../rust/fluent-fallback/src/generator.rs | 30 + third_party/rust/fluent-fallback/src/lib.rs | 104 ++ .../rust/fluent-fallback/src/localization.rs | 129 ++ .../fluent-fallback/src/pin_cell/README.md | 2 + .../rust/fluent-fallback/src/pin_cell/mod.rs | 97 ++ .../fluent-fallback/src/pin_cell/pin_mut.rs | 50 + .../fluent-fallback/src/pin_cell/pin_ref.rs | 47 + third_party/rust/fluent-fallback/src/types.rs | 29 + .../tests/localization_test.rs | 490 ++++++++ .../tests/resources/en-US/test.ftl | 4 + .../tests/resources/en-US/test2.ftl | 10 + .../tests/resources/pl/test.ftl | 4 + .../tests/resources/pl/test2.ftl | 9 + .../futures-executor/.cargo-checksum.json | 1 + third_party/rust/futures-executor/Cargo.toml | 47 + .../rust/futures-executor/LICENSE-APACHE | 202 +++ third_party/rust/futures-executor/LICENSE-MIT | 26 + .../futures-executor/benches/thread_notify.rs | 109 ++ .../rust/futures-executor/src/enter.rs | 80 ++ third_party/rust/futures-executor/src/lib.rs | 67 + .../rust/futures-executor/src/local_pool.rs | 400 ++++++ .../rust/futures-executor/src/thread_pool.rs | 375 ++++++ .../rust/futures-executor/src/unpark_mutex.rs | 137 +++ .../rust/futures-executor/tests/local_pool.rs | 434 +++++++ .../rust/futures-macro/.cargo-checksum.json | 1 + third_party/rust/futures-macro/Cargo.toml | 41 + third_party/rust/futures-macro/LICENSE-APACHE | 202 +++ third_party/rust/futures-macro/LICENSE-MIT | 26 + third_party/rust/futures-macro/build.rs | 28 + .../rust/futures-macro/src/executor.rs | 55 + third_party/rust/futures-macro/src/join.rs | 143 +++ third_party/rust/futures-macro/src/lib.rs | 54 + third_party/rust/futures-macro/src/select.rs | 330 +++++ .../rust/l10nregistry/.cargo-checksum.json | 1 + third_party/rust/l10nregistry/Cargo.toml | 63 + third_party/rust/l10nregistry/LICENSE-APACHE | 201 +++ third_party/rust/l10nregistry/LICENSE-MIT | 19 + .../rust/l10nregistry/benches/localization.rs | 70 ++ .../rust/l10nregistry/benches/preferences.rs | 57 + .../rust/l10nregistry/benches/solver.rs | 120 ++ .../rust/l10nregistry/benches/source.rs | 58 + third_party/rust/l10nregistry/src/env.rs | 5 + third_party/rust/l10nregistry/src/errors.rs | 62 + third_party/rust/l10nregistry/src/fluent.rs | 5 + third_party/rust/l10nregistry/src/lib.rs | 8 + .../l10nregistry/src/registry/asynchronous.rs | 207 ++++ .../rust/l10nregistry/src/registry/mod.rs | 236 ++++ .../l10nregistry/src/registry/synchronous.rs | 235 ++++ .../rust/l10nregistry/src/solver/README.md | 240 ++++ .../rust/l10nregistry/src/solver/mod.rs | 122 ++ .../rust/l10nregistry/src/solver/parallel.rs | 185 +++ .../rust/l10nregistry/src/solver/serial.rs | 88 ++ .../l10nregistry/src/solver/testing/mod.rs | 29 + .../src/solver/testing/scenarios.rs | 151 +++ .../rust/l10nregistry/src/source/fetcher.rs | 29 + .../rust/l10nregistry/src/source/mod.rs | 468 +++++++ third_party/rust/l10nregistry/src/testing.rs | 295 +++++ .../rust/l10nregistry/tests/localization.rs | 183 +++ .../rust/l10nregistry/tests/registry.rs | 199 +++ .../rust/l10nregistry/tests/scenarios.rs | 55 + third_party/rust/l10nregistry/tests/source.rs | 141 +++ .../rust/ouroboros/.cargo-checksum.json | 2 +- third_party/rust/ouroboros/Cargo.toml | 4 +- third_party/rust/ouroboros/src/lib.rs | 132 +- .../rust/ouroboros_macro/.cargo-checksum.json | 2 +- third_party/rust/ouroboros_macro/Cargo.toml | 3 +- third_party/rust/ouroboros_macro/src/lib.rs | 373 +++++- .../proc-macro-nested/.cargo-checksum.json | 1 + third_party/rust/proc-macro-nested/Cargo.toml | 21 + .../rust/proc-macro-nested/LICENSE-APACHE | 201 +++ .../rust/proc-macro-nested/LICENSE-MIT | 25 + third_party/rust/proc-macro-nested/build.rs | 50 + third_party/rust/proc-macro-nested/src/lib.rs | 69 ++ .../rust/replace_with/.cargo-checksum.json | 1 + third_party/rust/replace_with/Cargo.toml | 38 + .../rust/replace_with/LICENSE-APACHE.txt | 201 +++ third_party/rust/replace_with/LICENSE-MIT.txt | 23 + third_party/rust/replace_with/README.md | 117 ++ .../rust/replace_with/azure-pipelines.yml | 32 + third_party/rust/replace_with/src/lib.rs | 596 +++++++++ toolkit/library/rust/shared/Cargo.toml | 2 + toolkit/library/rust/shared/lib.rs | 3 + 139 files changed, 15020 insertions(+), 114 deletions(-) create mode 100644 third_party/rust/async-trait/.cargo-checksum.json create mode 100644 third_party/rust/async-trait/Cargo.toml create mode 100644 third_party/rust/async-trait/LICENSE-APACHE create mode 100644 third_party/rust/async-trait/LICENSE-MIT create mode 100644 third_party/rust/async-trait/README.md create mode 100644 third_party/rust/async-trait/src/args.rs create mode 100644 third_party/rust/async-trait/src/expand.rs create mode 100644 third_party/rust/async-trait/src/lib.rs create mode 100644 third_party/rust/async-trait/src/lifetime.rs create mode 100644 third_party/rust/async-trait/src/parse.rs create mode 100644 third_party/rust/async-trait/src/receiver.rs create mode 100644 third_party/rust/async-trait/src/respan.rs create mode 100644 third_party/rust/async-trait/tests/compiletest.rs create mode 100644 third_party/rust/async-trait/tests/executor/mod.rs create mode 100644 third_party/rust/async-trait/tests/test.rs create mode 100644 third_party/rust/async-trait/tests/ui/bare-trait-object.rs create mode 100644 third_party/rust/async-trait/tests/ui/bare-trait-object.stderr create mode 100644 third_party/rust/async-trait/tests/ui/delimiter-span.rs create mode 100644 third_party/rust/async-trait/tests/ui/delimiter-span.stderr create mode 100644 third_party/rust/async-trait/tests/ui/missing-body.rs create mode 100644 third_party/rust/async-trait/tests/ui/missing-body.stderr create mode 100644 third_party/rust/async-trait/tests/ui/must-use.rs create mode 100644 third_party/rust/async-trait/tests/ui/must-use.stderr create mode 100644 third_party/rust/async-trait/tests/ui/self-span.rs create mode 100644 third_party/rust/async-trait/tests/ui/self-span.stderr create mode 100644 third_party/rust/async-trait/tests/ui/send-not-implemented.rs create mode 100644 third_party/rust/async-trait/tests/ui/send-not-implemented.stderr create mode 100644 third_party/rust/async-trait/tests/ui/unsupported-self.rs create mode 100644 third_party/rust/async-trait/tests/ui/unsupported-self.stderr create mode 100644 third_party/rust/chunky-vec/.cargo-checksum.json create mode 100644 third_party/rust/chunky-vec/Cargo.toml create mode 100644 third_party/rust/chunky-vec/LICENSE-APACHE create mode 100644 third_party/rust/chunky-vec/LICENSE-MIT create mode 100644 third_party/rust/chunky-vec/README.md create mode 100644 third_party/rust/chunky-vec/src/lib.rs create mode 100644 third_party/rust/fluent-fallback/.cargo-checksum.json create mode 100644 third_party/rust/fluent-fallback/CHANGELOG.md create mode 100644 third_party/rust/fluent-fallback/Cargo.lock create mode 100644 third_party/rust/fluent-fallback/Cargo.toml create mode 100644 third_party/rust/fluent-fallback/LICENSE-APACHE create mode 100644 third_party/rust/fluent-fallback/LICENSE-MIT create mode 100644 third_party/rust/fluent-fallback/README.md create mode 100644 third_party/rust/fluent-fallback/examples/resources/en-US/simple.ftl create mode 100644 third_party/rust/fluent-fallback/examples/resources/pl/simple.ftl create mode 100644 third_party/rust/fluent-fallback/examples/simple-fallback.rs create mode 100644 third_party/rust/fluent-fallback/src/bundles.rs create mode 100644 third_party/rust/fluent-fallback/src/cache.rs create mode 100644 third_party/rust/fluent-fallback/src/env.rs create mode 100644 third_party/rust/fluent-fallback/src/errors.rs create mode 100644 third_party/rust/fluent-fallback/src/generator.rs create mode 100644 third_party/rust/fluent-fallback/src/lib.rs create mode 100644 third_party/rust/fluent-fallback/src/localization.rs create mode 100644 third_party/rust/fluent-fallback/src/pin_cell/README.md create mode 100644 third_party/rust/fluent-fallback/src/pin_cell/mod.rs create mode 100644 third_party/rust/fluent-fallback/src/pin_cell/pin_mut.rs create mode 100644 third_party/rust/fluent-fallback/src/pin_cell/pin_ref.rs create mode 100644 third_party/rust/fluent-fallback/src/types.rs create mode 100644 third_party/rust/fluent-fallback/tests/localization_test.rs create mode 100644 third_party/rust/fluent-fallback/tests/resources/en-US/test.ftl create mode 100644 third_party/rust/fluent-fallback/tests/resources/en-US/test2.ftl create mode 100644 third_party/rust/fluent-fallback/tests/resources/pl/test.ftl create mode 100644 third_party/rust/fluent-fallback/tests/resources/pl/test2.ftl create mode 100644 third_party/rust/futures-executor/.cargo-checksum.json create mode 100644 third_party/rust/futures-executor/Cargo.toml create mode 100644 third_party/rust/futures-executor/LICENSE-APACHE create mode 100644 third_party/rust/futures-executor/LICENSE-MIT create mode 100644 third_party/rust/futures-executor/benches/thread_notify.rs create mode 100644 third_party/rust/futures-executor/src/enter.rs create mode 100644 third_party/rust/futures-executor/src/lib.rs create mode 100644 third_party/rust/futures-executor/src/local_pool.rs create mode 100644 third_party/rust/futures-executor/src/thread_pool.rs create mode 100644 third_party/rust/futures-executor/src/unpark_mutex.rs create mode 100644 third_party/rust/futures-executor/tests/local_pool.rs create mode 100644 third_party/rust/futures-macro/.cargo-checksum.json create mode 100644 third_party/rust/futures-macro/Cargo.toml create mode 100644 third_party/rust/futures-macro/LICENSE-APACHE create mode 100644 third_party/rust/futures-macro/LICENSE-MIT create mode 100644 third_party/rust/futures-macro/build.rs create mode 100644 third_party/rust/futures-macro/src/executor.rs create mode 100644 third_party/rust/futures-macro/src/join.rs create mode 100644 third_party/rust/futures-macro/src/lib.rs create mode 100644 third_party/rust/futures-macro/src/select.rs create mode 100644 third_party/rust/l10nregistry/.cargo-checksum.json create mode 100644 third_party/rust/l10nregistry/Cargo.toml create mode 100644 third_party/rust/l10nregistry/LICENSE-APACHE create mode 100644 third_party/rust/l10nregistry/LICENSE-MIT create mode 100644 third_party/rust/l10nregistry/benches/localization.rs create mode 100644 third_party/rust/l10nregistry/benches/preferences.rs create mode 100644 third_party/rust/l10nregistry/benches/solver.rs create mode 100644 third_party/rust/l10nregistry/benches/source.rs create mode 100644 third_party/rust/l10nregistry/src/env.rs create mode 100644 third_party/rust/l10nregistry/src/errors.rs create mode 100644 third_party/rust/l10nregistry/src/fluent.rs create mode 100644 third_party/rust/l10nregistry/src/lib.rs create mode 100644 third_party/rust/l10nregistry/src/registry/asynchronous.rs create mode 100644 third_party/rust/l10nregistry/src/registry/mod.rs create mode 100644 third_party/rust/l10nregistry/src/registry/synchronous.rs create mode 100644 third_party/rust/l10nregistry/src/solver/README.md create mode 100644 third_party/rust/l10nregistry/src/solver/mod.rs create mode 100644 third_party/rust/l10nregistry/src/solver/parallel.rs create mode 100644 third_party/rust/l10nregistry/src/solver/serial.rs create mode 100644 third_party/rust/l10nregistry/src/solver/testing/mod.rs create mode 100644 third_party/rust/l10nregistry/src/solver/testing/scenarios.rs create mode 100644 third_party/rust/l10nregistry/src/source/fetcher.rs create mode 100644 third_party/rust/l10nregistry/src/source/mod.rs create mode 100644 third_party/rust/l10nregistry/src/testing.rs create mode 100644 third_party/rust/l10nregistry/tests/localization.rs create mode 100644 third_party/rust/l10nregistry/tests/registry.rs create mode 100644 third_party/rust/l10nregistry/tests/scenarios.rs create mode 100644 third_party/rust/l10nregistry/tests/source.rs create mode 100644 third_party/rust/proc-macro-nested/.cargo-checksum.json create mode 100644 third_party/rust/proc-macro-nested/Cargo.toml create mode 100644 third_party/rust/proc-macro-nested/LICENSE-APACHE create mode 100644 third_party/rust/proc-macro-nested/LICENSE-MIT create mode 100644 third_party/rust/proc-macro-nested/build.rs create mode 100644 third_party/rust/proc-macro-nested/src/lib.rs create mode 100644 third_party/rust/replace_with/.cargo-checksum.json create mode 100644 third_party/rust/replace_with/Cargo.toml create mode 100644 third_party/rust/replace_with/LICENSE-APACHE.txt create mode 100644 third_party/rust/replace_with/LICENSE-MIT.txt create mode 100644 third_party/rust/replace_with/README.md create mode 100644 third_party/rust/replace_with/azure-pipelines.yml create mode 100755 third_party/rust/replace_with/src/lib.rs diff --git a/.cargo/config.in b/.cargo/config.in index ec0effad085b..f0270413bca0 100644 --- a/.cargo/config.in +++ b/.cargo/config.in @@ -2,6 +2,11 @@ # It was generated by `mach vendor rust`. # Please do not edit. +[source."https://github.com/zbraniecki/l10nregistry-rs"] +git = "https://github.com/zbraniecki/l10nregistry-rs" +replace-with = "vendored-sources" +rev = "92d8fbfbbbdffa2047ce01a935a389eb11031f69" + [source."https://github.com/shravanrn/nix/"] git = "https://github.com/shravanrn/nix/" replace-with = "vendored-sources" diff --git a/Cargo.lock b/Cargo.lock index 5ed06d3c631f..13968063d8a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7,10 +7,6 @@ name = "Inflector" version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" -dependencies = [ - "lazy_static", - "regex", -] [[package]] name = "adler" @@ -95,6 +91,17 @@ dependencies = [ "libc", ] +[[package]] +name = "async-trait" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3a45e77e34375a7923b1e8febb049bb011f064714a8e17a1a616fef01da13d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atomic" version = "0.4.6" @@ -583,6 +590,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "chunky-vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb7bdea464ae038f09197b82430b921c53619fc8d2bcaf7b151013b3ca008017" + [[package]] name = "clang-sys" version = "1.2.0" @@ -1427,9 +1440,9 @@ dependencies = [ [[package]] name = "fluent-bundle" -version = "0.15.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b589dfaa7e69ddf497be48cd0d184d7ff6e2cbb8186d1bb01c26d5cf5449a17" +checksum = "8acf044eeb4872d9dbf2667541fbf461f5965c57e343878ad0fb24b5793fa007" dependencies = [ "fluent-langneg", "fluent-syntax", @@ -1441,13 +1454,30 @@ dependencies = [ "unic-langid", ] +[[package]] +name = "fluent-fallback" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "768feaf8a77beababd5cf3bb1154597b7161eb5a555013a3ac594fe3ed8b18ee" +dependencies = [ + "async-trait", + "chunky-vec", + "fluent-bundle", + "futures 0.3.15", + "once_cell", + "unic-langid", +] + [[package]] name = "fluent-ffi" version = "0.1.0" dependencies = [ "fluent", + "fluent-fallback", "fluent-pseudo", + "futures 0.3.15", "intl-memoizer", + "l10nregistry", "nsstring", "thin-vec", "unic-langid", @@ -1595,6 +1625,7 @@ checksum = "0e7e43a803dae2fa37c1f6a8fe121e1f7bf9548b4dfc0522a42f34145dadfc27" dependencies = [ "futures-channel", "futures-core", + "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -1627,12 +1658,36 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "futures-executor" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "badaa6a909fac9e7236d0620a2f57f7664640c56575b71a7552fbd68deafab79" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acc499defb3b348f8d8f3f66415835a9131856ff7714bf10dadfc4ec4bdb29a1" +[[package]] +name = "futures-macro" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c40298486cdf52cc00cd6d6987892ba502c7656a16a4192a9992b1ccedd121" +dependencies = [ + "autocfg", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "futures-sink" version = "0.3.15" @@ -1652,11 +1707,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "feb5c238d27e2bf94ffdfd27b2c29e3df4a68c4193bb6427384259e2bf191967" dependencies = [ "autocfg", + "futures-channel", "futures-core", + "futures-io", + "futures-macro", "futures-sink", "futures-task", + "memchr", "pin-project-lite 0.2.6", "pin-utils", + "proc-macro-hack", + "proc-macro-nested", + "slab", ] [[package]] @@ -1928,6 +1990,7 @@ dependencies = [ "cubeb-sys", "encoding_glue", "fluent", + "fluent-fallback", "fluent-ffi", "fluent-langneg", "fluent-langneg-ffi", @@ -1939,6 +2002,7 @@ dependencies = [ "http_sfv", "jsrust_shared", "kvstore", + "l10nregistry", "l10nregistry-ffi", "lmdb-rkv-sys", "log", @@ -2573,6 +2637,21 @@ dependencies = [ "xpcom", ] +[[package]] +name = "l10nregistry" +version = "0.2.0" +source = "git+https://github.com/zbraniecki/l10nregistry-rs?rev=92d8fbfbbbdffa2047ce01a935a389eb11031f69#92d8fbfbbbdffa2047ce01a935a389eb11031f69" +dependencies = [ + "async-trait", + "fluent-bundle", + "fluent-fallback", + "futures 0.3.15", + "pin-project-lite 0.2.6", + "replace_with", + "rustc-hash", + "unic-langid", +] + [[package]] name = "l10nregistry-ffi" version = "0.1.0" @@ -3503,9 +3582,9 @@ dependencies = [ [[package]] name = "ouroboros" -version = "0.8.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069fb33e127cabdc8ad6a287eed9719b85c612d36199777f6dc41ad91f7be41a" +checksum = "c8234affc3c31a8b744cc236fd3dc7443f57c6370cbb7d61f41f9c7dcc6c2530" dependencies = [ "ouroboros_macro", "stable_deref_trait", @@ -3513,9 +3592,9 @@ dependencies = [ [[package]] name = "ouroboros_macro" -version = "0.8.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad938cc920f299d6dce91e43d3ce316e785f4aa4bc4243555634dc2967098fc6" +checksum = "3633332cd8c0b6a865e2e0e705fad9cde25fe458cd0c693629b58a7b15e4d852" dependencies = [ "Inflector", "proc-macro-error", @@ -3824,6 +3903,12 @@ version = "0.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" +[[package]] +name = "proc-macro-nested" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" + [[package]] name = "proc-macro2" version = "1.0.27" @@ -4130,6 +4215,12 @@ dependencies = [ "syn", ] +[[package]] +name = "replace_with" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a8614ee435691de62bcffcf4a66d91b3594bf1428a5722e79103249a095690" + [[package]] name = "ringbuf" version = "0.2.5" diff --git a/intl/l10n/rust/fluent-ffi/Cargo.toml b/intl/l10n/rust/fluent-ffi/Cargo.toml index 4ed249ed8e2e..6e9ac0c77c17 100644 --- a/intl/l10n/rust/fluent-ffi/Cargo.toml +++ b/intl/l10n/rust/fluent-ffi/Cargo.toml @@ -5,9 +5,12 @@ authors = ["Zibi Braniecki "] edition = "2018" [dependencies] -fluent = { version = "0.15.0", features = ["fluent-pseudo"] } +fluent = { version = "0.15", features = ["fluent-pseudo"] } fluent-pseudo = "0.2.3" intl-memoizer = "0.5" unic-langid = "0.9" nsstring = { path = "../../../../xpcom/rust/nsstring" } thin-vec = { version = "0.2.1", features = ["gecko-ffi"] } +l10nregistry = { git = "https://github.com/zbraniecki/l10nregistry-rs", rev = "92d8fbfbbbdffa2047ce01a935a389eb11031f69" } +fluent-fallback = "0.5" +futures = "0.3" diff --git a/third_party/rust/async-trait/.cargo-checksum.json b/third_party/rust/async-trait/.cargo-checksum.json new file mode 100644 index 000000000000..0b9c44ea5872 --- /dev/null +++ b/third_party/rust/async-trait/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"4c50bdb04cb7b2c2eccf4f6f43a27bd833e3481eaaeaf44ce31e0e4842749f98","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3","README.md":"7cdf53d90efa27ae464b138755eb88a7f912ff95cad3ea3a1970ee4891af8aa2","src/args.rs":"6eed5497db91752b3aae597943c39e769f60406b37055304e69e4699f1f87b15","src/expand.rs":"da89e09db97138bc7488fde2aea105329635a4467267d7cfe5d80d4550948e2a","src/lib.rs":"fee50ccfc315bb468f4207cebef12ec7285cef78c5c09c9f09d5a32b1876f4af","src/lifetime.rs":"e51bd32dba9caf32ea241d2f4fbd83c29b5c71d0308d9c18268b9e4594ec9c76","src/parse.rs":"cd9032fe2c6dcf41050b3a59b9fb98eb9700a29bbe2fa011ee2854014c1666b7","src/receiver.rs":"0a166f7423f17e0743b64a37c448dcbace89f1df71d204f94b0ed942c7d9e053","src/respan.rs":"eef5b4467f58e1df4650a2331495c5e28eff8d54a3250506fc2f6c2da3366ada","tests/compiletest.rs":"0a52a44786aea1c299c695bf948b2ed2081e4cc344e5c2cadceab4eb03d0010d","tests/executor/mod.rs":"620975b33cc2a494efb6bce230cf285a462b85e5fe12d71561b5bacd9d507b28","tests/test.rs":"78dcb0b36fa1351dd97f1d415889e1178799667ffef70daa115695ee99758e71","tests/ui/bare-trait-object.rs":"4546e8bd6682de11920fa4c768295fed61954484ef0550dfadbc5677b77f29a5","tests/ui/bare-trait-object.stderr":"b63287594e6e5183e9b5b0e701fc4dd3a9b4fb0ea93eb910be56166e5fa4cdbd","tests/ui/delimiter-span.rs":"5e11150b1448f4c5b88a08aa579a663606ccaaed7f302e9465b42fc3489f0f3e","tests/ui/delimiter-span.stderr":"a1d7ead68b8d01e6e51c0943ccd683fb5fb8c3eae2d56feb22b0ef777949b87d","tests/ui/missing-body.rs":"d06c0da8c6044e7c790b924136f167e2edc0d0d3fa01f23521f3f08ca605929b","tests/ui/missing-body.stderr":"636a03cc42933b59d73032ce6cea862e33c16efb9c7fe7f27749247998bc9f23","tests/ui/must-use.rs":"75090c7df984df0996464337f60371d198bd0caf3f9f44b10d1e131f15fd4fca","tests/ui/must-use.stderr":"e6cb190e02f0226df6444065aaca3051f7db8ae599bba18a685155c52bb799b6","tests/ui/self-span.rs":"67ddde05907d7014bfb3f2c63d427b1d72d6c4369a9108a4335dac6bee5832b2","tests/ui/self-span.stderr":"8f473640c732ce66656f7da611c8173c3f8719ad90fa42d7043bb037f43964ea","tests/ui/send-not-implemented.rs":"5f1dd26f4eb34c653048e9658fbf6b41e2930d6222869b397ca4d8d7113a2809","tests/ui/send-not-implemented.stderr":"f7ea7ed8fe196c4bfc27c028ea3e1c0253e05e45e082454dbfba7ef0655d3fa6","tests/ui/unsupported-self.rs":"f7855bc39dab1fd2f533fb2e873a27c3757dcb9fb57001e4b19f58d3dda36d01","tests/ui/unsupported-self.stderr":"2b30517b790c666ea85442afc1c701ddc235d091372e175009aa084bd57b7070"},"package":"8d3a45e77e34375a7923b1e8febb049bb011f064714a8e17a1a616fef01da13d"} \ No newline at end of file diff --git a/third_party/rust/async-trait/Cargo.toml b/third_party/rust/async-trait/Cargo.toml new file mode 100644 index 000000000000..0f2196317eea --- /dev/null +++ b/third_party/rust/async-trait/Cargo.toml @@ -0,0 +1,51 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies +# +# If you believe there's an error in this file please file an +# issue against the rust-lang/cargo repository. If you're +# editing this file be aware that the upstream Cargo.toml +# will likely look very different (and much more reasonable) + +[package] +edition = "2018" +name = "async-trait" +version = "0.1.42" +authors = ["David Tolnay "] +description = "Type erasure for async trait methods" +documentation = "https://docs.rs/async-trait" +readme = "README.md" +license = "MIT OR Apache-2.0" +repository = "https://github.com/dtolnay/async-trait" +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[lib] +proc-macro = true +[dependencies.proc-macro2] +version = "1.0" + +[dependencies.quote] +version = "1.0" + +[dependencies.syn] +version = "1.0" +features = ["full", "visit-mut"] +[dev-dependencies.rustversion] +version = "1.0" + +[dev-dependencies.tracing] +version = "0.1.14" + +[dev-dependencies.tracing-attributes] +version = "0.1.8" + +[dev-dependencies.tracing-futures] +version = "0.2" + +[dev-dependencies.trybuild] +version = "1.0.19" +features = ["diff"] diff --git a/third_party/rust/async-trait/LICENSE-APACHE b/third_party/rust/async-trait/LICENSE-APACHE new file mode 100644 index 000000000000..16fe87b06e80 --- /dev/null +++ b/third_party/rust/async-trait/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/third_party/rust/async-trait/LICENSE-MIT b/third_party/rust/async-trait/LICENSE-MIT new file mode 100644 index 000000000000..31aa79387f27 --- /dev/null +++ b/third_party/rust/async-trait/LICENSE-MIT @@ -0,0 +1,23 @@ +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/third_party/rust/async-trait/README.md b/third_party/rust/async-trait/README.md new file mode 100644 index 000000000000..c393c10ca6c4 --- /dev/null +++ b/third_party/rust/async-trait/README.md @@ -0,0 +1,262 @@ +Async trait methods +=================== + +[github](https://github.com/dtolnay/async-trait) +[crates.io](https://crates.io/crates/async-trait) +[docs.rs](https://docs.rs/async-trait) +[build status](https://github.com/dtolnay/async-trait/actions?query=branch%3Amaster) + +The initial round of stabilizations for the async/await language feature in Rust +1.39 did not include support for async fn in traits. Trying to include an async +fn in a trait produces the following error: + +```rust +trait MyTrait { + async fn f() {} +} +``` + +```console +error[E0706]: trait fns cannot be declared `async` + --> src/main.rs:4:5 + | +4 | async fn f() {} + | ^^^^^^^^^^^^^^^ +``` + +This crate provides an attribute macro to make async fn in traits work. + +Please refer to [*why async fn in traits are hard*][hard] for a deeper analysis +of how this implementation differs from what the compiler and language hope to +deliver in the future. + +[hard]: https://smallcultfollowing.com/babysteps/blog/2019/10/26/async-fn-in-traits-are-hard/ + +
+ +## Example + +This example implements the core of a highly effective advertising platform +using async fn in a trait. + +The only thing to notice here is that we write an `#[async_trait]` macro on top +of traits and trait impls that contain async fn, and then they work. + +```rust +use async_trait::async_trait; + +#[async_trait] +trait Advertisement { + async fn run(&self); +} + +struct Modal; + +#[async_trait] +impl Advertisement for Modal { + async fn run(&self) { + self.render_fullscreen().await; + for _ in 0..4u16 { + remind_user_to_join_mailing_list().await; + } + self.hide_for_now().await; + } +} + +struct AutoplayingVideo { + media_url: String, +} + +#[async_trait] +impl Advertisement for AutoplayingVideo { + async fn run(&self) { + let stream = connect(&self.media_url).await; + stream.play().await; + + // Video probably persuaded user to join our mailing list! + Modal.run().await; + } +} +``` + +
+ +## Supported features + +It is the intention that all features of Rust traits should work nicely with +\#\[async_trait\], but the edge cases are numerous. *Please file an issue if you +see unexpected borrow checker errors, type errors, or warnings.* There is no use +of `unsafe` in the expanded code, so rest assured that if your code compiles it +can't be that badly broken. + +- 👍 Self by value, by reference, by mut reference, or no self; +- 👍 Any number of arguments, any return value; +- 👍 Generic type parameters and lifetime parameters; +- 👍 Associated types; +- 👍 Having async and non-async functions in the same trait; +- 👍 Default implementations provided by the trait; +- 👍 Elided lifetimes; +- 👍 Dyn-capable traits. + +
+ +## Explanation + +Async fns get transformed into methods that return `Pin>` and delegate to a private async freestanding function. + +For example the `impl Advertisement for AutoplayingVideo` above would be +expanded as: + +```rust +impl Advertisement for AutoplayingVideo { + fn run<'async_trait>( + &'async_trait self, + ) -> Pin + Send + 'async_trait>> + where + Self: Sync + 'async_trait, + { + async fn run(_self: &AutoplayingVideo) { + /* the original method body */ + } + + Box::pin(run(self)) + } +} +``` + +
+ +## Non-threadsafe futures + +Not all async traits need futures that are `dyn Future + Send`. To avoid having +Send and Sync bounds placed on the async trait methods, invoke the async trait +macro as `#[async_trait(?Send)]` on both the trait and the impl blocks. + +
+ +## Elided lifetimes + +Be aware that async fn syntax does not allow lifetime elision outside of `&` and +`&mut` references. (This is true even when not using #\[async_trait\].) +Lifetimes must be named or marked by the placeholder `'_`. + +Fortunately the compiler is able to diagnose missing lifetimes with a good error +message. + +```rust +type Elided<'a> = &'a usize; + +#[async_trait] +trait Test { + async fn test(not_okay: Elided, okay: &usize) {} +} +``` + +```console +error[E0726]: implicit elided lifetime not allowed here + --> src/main.rs:9:29 + | +9 | async fn test(not_okay: Elided, okay: &usize) {} + | ^^^^^^- help: indicate the anonymous lifetime: `<'_>` +``` + +The fix is to name the lifetime or use `'_`. + +```rust +#[async_trait] +trait Test { + // either + async fn test<'e>(elided: Elided<'e>) {} + // or + async fn test(elided: Elided<'_>) {} +} +``` + +
+ +## Dyn traits + +Traits with async methods can be used as trait objects as long as they meet the +usual requirements for dyn -- no methods with type parameters, no self by value, +no associated types, etc. + +```rust +#[async_trait] +pub trait ObjectSafe { + async fn f(&self); + async fn g(&mut self); +} + +impl ObjectSafe for MyType {...} + +let value: MyType = ...; +let object = &value as &dyn ObjectSafe; // make trait object +``` + +The one wrinkle is in traits that provide default implementations of async +methods. In order for the default implementation to produce a future that is +Send, the async\_trait macro must emit a bound of `Self: Sync` on trait methods +that take `&self` and a bound `Self: Send` on trait methods that take `&mut +self`. An example of the former is visible in the expanded code in the +explanation section above. + +If you make a trait with async methods that have default implementations, +everything will work except that the trait cannot be used as a trait object. +Creating a value of type `&dyn Trait` will produce an error that looks like +this: + +```console +error: the trait `Test` cannot be made into an object + --> src/main.rs:8:5 + | +8 | async fn cannot_dyn(&self) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +``` + +For traits that need to be object safe and need to have default implementations +for some async methods, there are two resolutions. Either you can add Send +and/or Sync as supertraits (Send if there are `&mut self` methods with default +implementations, Sync if there are `&self` methods with default implementions) +to constrain all implementors of the trait such that the default implementations +are applicable to them: + +```rust +#[async_trait] +pub trait ObjectSafe: Sync { // added supertrait + async fn can_dyn(&self) {} +} + +let object = &value as &dyn ObjectSafe; +``` + +or you can strike the problematic methods from your trait object by bounding +them with `Self: Sized`: + +```rust +#[async_trait] +pub trait ObjectSafe { + async fn cannot_dyn(&self) where Self: Sized {} + + // presumably other methods +} + +let object = &value as &dyn ObjectSafe; +``` + +
+ +#### License + + +Licensed under either of Apache License, Version +2.0 or MIT license at your option. + + +
+ + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in this crate by you, as defined in the Apache-2.0 license, shall +be dual licensed as above, without any additional terms or conditions. + diff --git a/third_party/rust/async-trait/src/args.rs b/third_party/rust/async-trait/src/args.rs new file mode 100644 index 000000000000..72d97e9525b3 --- /dev/null +++ b/third_party/rust/async-trait/src/args.rs @@ -0,0 +1,36 @@ +use proc_macro2::Span; +use syn::parse::{Error, Parse, ParseStream, Result}; +use syn::Token; + +#[derive(Copy, Clone)] +pub struct Args { + pub local: bool, +} + +mod kw { + syn::custom_keyword!(Send); +} + +impl Parse for Args { + fn parse(input: ParseStream) -> Result { + match try_parse(input) { + Ok(args) if input.is_empty() => Ok(args), + _ => Err(error()), + } + } +} + +fn try_parse(input: ParseStream) -> Result { + if input.peek(Token![?]) { + input.parse::()?; + input.parse::()?; + Ok(Args { local: true }) + } else { + Ok(Args { local: false }) + } +} + +fn error() -> Error { + let msg = "expected #[async_trait] or #[async_trait(?Send)]"; + Error::new(Span::call_site(), msg) +} diff --git a/third_party/rust/async-trait/src/expand.rs b/third_party/rust/async-trait/src/expand.rs new file mode 100644 index 000000000000..fb83df12ec0f --- /dev/null +++ b/third_party/rust/async-trait/src/expand.rs @@ -0,0 +1,502 @@ +use crate::lifetime::{has_async_lifetime, CollectLifetimes}; +use crate::parse::Item; +use crate::receiver::{ + has_self_in_block, has_self_in_sig, has_self_in_where_predicate, ReplaceReceiver, +}; +use proc_macro2::{Span, TokenStream}; +use quote::{format_ident, quote, quote_spanned, ToTokens}; +use std::mem; +use syn::punctuated::Punctuated; +use syn::visit_mut::VisitMut; +use syn::{ + parse_quote, Block, FnArg, GenericParam, Generics, Ident, ImplItem, Lifetime, Pat, PatIdent, + Path, Receiver, ReturnType, Signature, Stmt, Token, TraitItem, Type, TypeParam, TypeParamBound, + WhereClause, +}; + +impl ToTokens for Item { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Item::Trait(item) => item.to_tokens(tokens), + Item::Impl(item) => item.to_tokens(tokens), + } + } +} + +#[derive(Clone, Copy)] +enum Context<'a> { + Trait { + name: &'a Ident, + generics: &'a Generics, + supertraits: &'a Supertraits, + }, + Impl { + impl_generics: &'a Generics, + receiver: &'a Type, + as_trait: &'a Path, + }, +} + +impl Context<'_> { + fn lifetimes<'a>(&'a self, used: &'a [Lifetime]) -> impl Iterator { + let generics = match self { + Context::Trait { generics, .. } => generics, + Context::Impl { impl_generics, .. } => impl_generics, + }; + generics.params.iter().filter(move |param| { + if let GenericParam::Lifetime(param) = param { + used.contains(¶m.lifetime) + } else { + false + } + }) + } +} + +type Supertraits = Punctuated; + +pub fn expand(input: &mut Item, is_local: bool) { + match input { + Item::Trait(input) => { + let context = Context::Trait { + name: &input.ident, + generics: &input.generics, + supertraits: &input.supertraits, + }; + for inner in &mut input.items { + if let TraitItem::Method(method) = inner { + let sig = &mut method.sig; + if sig.asyncness.is_some() { + let block = &mut method.default; + let mut has_self = has_self_in_sig(sig); + if let Some(block) = block { + has_self |= has_self_in_block(block); + transform_block(context, sig, block, has_self, is_local); + method + .attrs + .push(parse_quote!(#[allow(clippy::used_underscore_binding)])); + } + let has_default = method.default.is_some(); + transform_sig(context, sig, has_self, has_default, is_local); + method.attrs.push(parse_quote!(#[must_use])); + } + } + } + } + Item::Impl(input) => { + let mut lifetimes = CollectLifetimes::new("'impl"); + lifetimes.visit_type_mut(&mut *input.self_ty); + lifetimes.visit_path_mut(&mut input.trait_.as_mut().unwrap().1); + let params = &input.generics.params; + let elided = lifetimes.elided; + input.generics.params = parse_quote!(#(#elided,)* #params); + + let context = Context::Impl { + impl_generics: &input.generics, + receiver: &input.self_ty, + as_trait: &input.trait_.as_ref().unwrap().1, + }; + for inner in &mut input.items { + if let ImplItem::Method(method) = inner { + let sig = &mut method.sig; + if sig.asyncness.is_some() { + let block = &mut method.block; + let has_self = has_self_in_sig(sig) || has_self_in_block(block); + transform_block(context, sig, block, has_self, is_local); + transform_sig(context, sig, has_self, false, is_local); + method + .attrs + .push(parse_quote!(#[allow(clippy::used_underscore_binding)])); + } + } + } + } + } +} + +// Input: +// async fn f(&self, x: &T) -> Ret; +// +// Output: +// fn f<'life0, 'life1, 'async_trait, T>( +// &'life0 self, +// x: &'life1 T, +// ) -> Pin + Send + 'async_trait>> +// where +// 'life0: 'async_trait, +// 'life1: 'async_trait, +// T: 'async_trait, +// Self: Sync + 'async_trait; +fn transform_sig( + context: Context, + sig: &mut Signature, + has_self: bool, + has_default: bool, + is_local: bool, +) { + sig.fn_token.span = sig.asyncness.take().unwrap().span; + + let ret = match &sig.output { + ReturnType::Default => quote!(()), + ReturnType::Type(_, ret) => quote!(#ret), + }; + + let mut lifetimes = CollectLifetimes::new("'life"); + for arg in sig.inputs.iter_mut() { + match arg { + FnArg::Receiver(arg) => lifetimes.visit_receiver_mut(arg), + FnArg::Typed(arg) => lifetimes.visit_type_mut(&mut arg.ty), + } + } + + let where_clause = sig + .generics + .where_clause + .get_or_insert_with(|| WhereClause { + where_token: Default::default(), + predicates: Punctuated::new(), + }); + for param in sig + .generics + .params + .iter() + .chain(context.lifetimes(&lifetimes.explicit)) + { + match param { + GenericParam::Type(param) => { + let param = ¶m.ident; + where_clause + .predicates + .push(parse_quote!(#param: 'async_trait)); + } + GenericParam::Lifetime(param) => { + let param = ¶m.lifetime; + where_clause + .predicates + .push(parse_quote!(#param: 'async_trait)); + } + GenericParam::Const(_) => {} + } + } + for elided in lifetimes.elided { + sig.generics.params.push(parse_quote!(#elided)); + where_clause + .predicates + .push(parse_quote!(#elided: 'async_trait)); + } + sig.generics.params.push(parse_quote!('async_trait)); + if has_self { + let bound: Ident = match sig.inputs.iter().next() { + Some(FnArg::Receiver(Receiver { + reference: Some(_), + mutability: None, + .. + })) => parse_quote!(Sync), + Some(FnArg::Typed(arg)) + if match (arg.pat.as_ref(), arg.ty.as_ref()) { + (Pat::Ident(pat), Type::Reference(ty)) => { + pat.ident == "self" && ty.mutability.is_none() + } + _ => false, + } => + { + parse_quote!(Sync) + } + _ => parse_quote!(Send), + }; + let assume_bound = match context { + Context::Trait { supertraits, .. } => !has_default || has_bound(supertraits, &bound), + Context::Impl { .. } => true, + }; + where_clause.predicates.push(if assume_bound || is_local { + parse_quote!(Self: 'async_trait) + } else { + parse_quote!(Self: ::core::marker::#bound + 'async_trait) + }); + } + + for (i, arg) in sig.inputs.iter_mut().enumerate() { + match arg { + FnArg::Receiver(Receiver { + reference: Some(_), .. + }) => {} + FnArg::Receiver(arg) => arg.mutability = None, + FnArg::Typed(arg) => { + if let Pat::Ident(ident) = &mut *arg.pat { + ident.by_ref = None; + ident.mutability = None; + } else { + let positional = positional_arg(i); + *arg.pat = parse_quote!(#positional); + } + } + } + } + + let bounds = if is_local { + quote!('async_trait) + } else { + quote!(::core::marker::Send + 'async_trait) + }; + + sig.output = parse_quote! { + -> ::core::pin::Pin + #bounds + >> + }; +} + +// Input: +// async fn f(&self, x: &T) -> Ret { +// self + x +// } +// +// Output: +// async fn f(_self: &AsyncTrait, x: &T) -> Ret { +// _self + x +// } +// Box::pin(async_trait_method::(self, x)) +fn transform_block( + context: Context, + sig: &mut Signature, + block: &mut Block, + has_self: bool, + is_local: bool, +) { + if let Some(Stmt::Item(syn::Item::Verbatim(item))) = block.stmts.first() { + if block.stmts.len() == 1 && item.to_string() == ";" { + return; + } + } + + let inner = format_ident!("__{}", sig.ident); + let args = sig.inputs.iter().enumerate().map(|(i, arg)| match arg { + FnArg::Receiver(Receiver { self_token, .. }) => quote!(#self_token), + FnArg::Typed(arg) => { + if let Pat::Ident(PatIdent { ident, .. }) = &*arg.pat { + quote!(#ident) + } else { + positional_arg(i).into_token_stream() + } + } + }); + + let mut standalone = sig.clone(); + standalone.ident = inner.clone(); + + let generics = match context { + Context::Trait { generics, .. } => generics, + Context::Impl { impl_generics, .. } => impl_generics, + }; + + let mut outer_generics = generics.clone(); + for p in &mut outer_generics.params { + match p { + GenericParam::Type(t) => t.default = None, + GenericParam::Const(c) => c.default = None, + GenericParam::Lifetime(_) => {} + } + } + if !has_self { + if let Some(mut where_clause) = outer_generics.where_clause { + where_clause.predicates = where_clause + .predicates + .into_iter() + .filter_map(|mut pred| { + if has_self_in_where_predicate(&mut pred) { + None + } else { + Some(pred) + } + }) + .collect(); + outer_generics.where_clause = Some(where_clause); + } + } + + let fn_generics = mem::replace(&mut standalone.generics, outer_generics); + standalone.generics.params.extend(fn_generics.params); + if let Some(where_clause) = fn_generics.where_clause { + standalone + .generics + .make_where_clause() + .predicates + .extend(where_clause.predicates); + } + + if has_async_lifetime(&mut standalone, block) { + standalone.generics.params.push(parse_quote!('async_trait)); + } + + let mut types = standalone + .generics + .type_params() + .map(|param| param.ident.clone()) + .collect::>(); + + let mut self_bound = None::; + match standalone.inputs.iter_mut().next() { + Some( + arg @ FnArg::Receiver(Receiver { + reference: Some(_), .. + }), + ) => { + let (lifetime, mutability, self_token) = match arg { + FnArg::Receiver(Receiver { + reference: Some((_, lifetime)), + mutability, + self_token, + .. + }) => (lifetime, mutability, self_token), + _ => unreachable!(), + }; + let under_self = Ident::new("_self", self_token.span); + match context { + Context::Trait { .. } => { + self_bound = Some(match mutability { + Some(_) => parse_quote!(::core::marker::Send), + None => parse_quote!(::core::marker::Sync), + }); + *arg = parse_quote! { + #under_self: &#lifetime #mutability AsyncTrait + }; + } + Context::Impl { receiver, .. } => { + let mut ty = quote!(#receiver); + if let Type::TraitObject(trait_object) = receiver { + if trait_object.dyn_token.is_none() { + ty = quote!(dyn #ty); + } + if trait_object.bounds.len() > 1 { + ty = quote!((#ty)); + } + } + *arg = parse_quote! { + #under_self: &#lifetime #mutability #ty + }; + } + } + } + Some(arg @ FnArg::Receiver(_)) => { + let (self_token, mutability) = match arg { + FnArg::Receiver(Receiver { + self_token, + mutability, + .. + }) => (self_token, mutability), + _ => unreachable!(), + }; + let under_self = Ident::new("_self", self_token.span); + match context { + Context::Trait { .. } => { + self_bound = Some(parse_quote!(::core::marker::Send)); + *arg = parse_quote! { + #mutability #under_self: AsyncTrait + }; + } + Context::Impl { receiver, .. } => { + *arg = parse_quote! { + #mutability #under_self: #receiver + }; + } + } + } + Some(FnArg::Typed(arg)) => { + if let Pat::Ident(arg) = &mut *arg.pat { + if arg.ident == "self" { + arg.ident = Ident::new("_self", arg.ident.span()); + } + } + } + _ => {} + } + + if let Context::Trait { name, generics, .. } = context { + if has_self { + let (_, generics, _) = generics.split_for_impl(); + let mut self_param: TypeParam = parse_quote!(AsyncTrait: ?Sized + #name #generics); + if !is_local { + self_param.bounds.extend(self_bound); + } + let count = standalone + .generics + .params + .iter() + .take_while(|param| { + if let GenericParam::Const(_) = param { + false + } else { + true + } + }) + .count(); + standalone + .generics + .params + .insert(count, GenericParam::Type(self_param)); + types.push(Ident::new("Self", Span::call_site())); + } + } + + if let Some(where_clause) = &mut standalone.generics.where_clause { + // Work around an input bound like `where Self::Output: Send` expanding + // to `where ::Output: Send` which is illegal syntax because + // `where` is reserved for future use... :( + where_clause.predicates.insert(0, parse_quote!((): Sized)); + } + + let mut replace = match context { + Context::Trait { .. } => ReplaceReceiver::with(parse_quote!(AsyncTrait)), + Context::Impl { + receiver, as_trait, .. + } => ReplaceReceiver::with_as_trait(receiver.clone(), as_trait.clone()), + }; + replace.visit_signature_mut(&mut standalone); + replace.visit_block_mut(block); + + let mut generics = types; + let consts = standalone + .generics + .const_params() + .map(|param| param.ident.clone()); + generics.extend(consts); + + let allow_non_snake_case = if sig.ident != sig.ident.to_string().to_lowercase() { + Some(quote!(non_snake_case,)) + } else { + None + }; + + let brace = block.brace_token; + let box_pin = quote_spanned!(brace.span=> { + #[allow( + #allow_non_snake_case + unused_parens, // https://github.com/dtolnay/async-trait/issues/118 + clippy::missing_docs_in_private_items, + clippy::needless_lifetimes, + clippy::ptr_arg, + clippy::trivially_copy_pass_by_ref, + clippy::type_repetition_in_bounds, + clippy::used_underscore_binding, + )] + #standalone #block + Box::pin(#inner::<#(#generics),*>(#(#args),*)) + }); + *block = parse_quote!(#box_pin); + block.brace_token = brace; +} + +fn positional_arg(i: usize) -> Ident { + format_ident!("__arg{}", i) +} + +fn has_bound(supertraits: &Supertraits, marker: &Ident) -> bool { + for bound in supertraits { + if let TypeParamBound::Trait(bound) = bound { + if bound.path.is_ident(marker) { + return true; + } + } + } + false +} diff --git a/third_party/rust/async-trait/src/lib.rs b/third_party/rust/async-trait/src/lib.rs new file mode 100644 index 000000000000..929af4f60dc0 --- /dev/null +++ b/third_party/rust/async-trait/src/lib.rs @@ -0,0 +1,330 @@ +//! [![github]](https://github.com/dtolnay/async-trait) [![crates-io]](https://crates.io/crates/async-trait) [![docs-rs]](https://docs.rs/async-trait) +//! +//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github +//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust +//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K +//! +//!
+//! +//!
Type erasure for async trait methods
+//! +//! The initial round of stabilizations for the async/await language feature in +//! Rust 1.39 did not include support for async fn in traits. Trying to include +//! an async fn in a trait produces the following error: +//! +//! ```compile_fail +//! trait MyTrait { +//! async fn f() {} +//! } +//! ``` +//! +//! ```text +//! error[E0706]: trait fns cannot be declared `async` +//! --> src/main.rs:4:5 +//! | +//! 4 | async fn f() {} +//! | ^^^^^^^^^^^^^^^ +//! ``` +//! +//! This crate provides an attribute macro to make async fn in traits work. +//! +//! Please refer to [*why async fn in traits are hard*][hard] for a deeper +//! analysis of how this implementation differs from what the compiler and +//! language hope to deliver in the future. +//! +//! [hard]: https://smallcultfollowing.com/babysteps/blog/2019/10/26/async-fn-in-traits-are-hard/ +//! +//!
+//! +//! # Example +//! +//! This example implements the core of a highly effective advertising platform +//! using async fn in a trait. +//! +//! The only thing to notice here is that we write an `#[async_trait]` macro on +//! top of traits and trait impls that contain async fn, and then they work. +//! +//! ``` +//! use async_trait::async_trait; +//! +//! #[async_trait] +//! trait Advertisement { +//! async fn run(&self); +//! } +//! +//! struct Modal; +//! +//! #[async_trait] +//! impl Advertisement for Modal { +//! async fn run(&self) { +//! self.render_fullscreen().await; +//! for _ in 0..4u16 { +//! remind_user_to_join_mailing_list().await; +//! } +//! self.hide_for_now().await; +//! } +//! } +//! +//! struct AutoplayingVideo { +//! media_url: String, +//! } +//! +//! #[async_trait] +//! impl Advertisement for AutoplayingVideo { +//! async fn run(&self) { +//! let stream = connect(&self.media_url).await; +//! stream.play().await; +//! +//! // Video probably persuaded user to join our mailing list! +//! Modal.run().await; +//! } +//! } +//! # +//! # impl Modal { +//! # async fn render_fullscreen(&self) {} +//! # async fn hide_for_now(&self) {} +//! # } +//! # +//! # async fn remind_user_to_join_mailing_list() {} +//! # +//! # struct Stream; +//! # async fn connect(_media_url: &str) -> Stream { Stream } +//! # impl Stream { +//! # async fn play(&self) {} +//! # } +//! ``` +//! +//!

+//! +//! # Supported features +//! +//! It is the intention that all features of Rust traits should work nicely with +//! #\[async_trait\], but the edge cases are numerous. Please file an issue if +//! you see unexpected borrow checker errors, type errors, or warnings. There is +//! no use of `unsafe` in the expanded code, so rest assured that if your code +//! compiles it can't be that badly broken. +//! +//! > ☑ Self by value, by reference, by mut reference, or no self;
+//! > ☑ Any number of arguments, any return value;
+//! > ☑ Generic type parameters and lifetime parameters;
+//! > ☑ Associated types;
+//! > ☑ Having async and non-async functions in the same trait;
+//! > ☑ Default implementations provided by the trait;
+//! > ☑ Elided lifetimes;
+//! > ☑ Dyn-capable traits.
+//! +//!
+//! +//! # Explanation +//! +//! Async fns get transformed into methods that return `Pin>` and delegate to a private async freestanding function. +//! +//! For example the `impl Advertisement for AutoplayingVideo` above would be +//! expanded as: +//! +//! ``` +//! # const IGNORE: &str = stringify! { +//! impl Advertisement for AutoplayingVideo { +//! fn run<'async>( +//! &'async self, +//! ) -> Pin + Send + 'async>> +//! where +//! Self: Sync + 'async, +//! { +//! async fn run(_self: &AutoplayingVideo) { +//! /* the original method body */ +//! } +//! +//! Box::pin(run(self)) +//! } +//! } +//! # }; +//! ``` +//! +//!

+//! +//! # Non-threadsafe futures +//! +//! Not all async traits need futures that are `dyn Future + Send`. To avoid +//! having Send and Sync bounds placed on the async trait methods, invoke the +//! async trait macro as `#[async_trait(?Send)]` on both the trait and the impl +//! blocks. +//! +//!
+//! +//! # Elided lifetimes +//! +//! Be aware that async fn syntax does not allow lifetime elision outside of `&` +//! and `&mut` references. (This is true even when not using #\[async_trait\].) +//! Lifetimes must be named or marked by the placeholder `'_`. +//! +//! Fortunately the compiler is able to diagnose missing lifetimes with a good +//! error message. +//! +//! ```compile_fail +//! # use async_trait::async_trait; +//! # +//! type Elided<'a> = &'a usize; +//! +//! #[async_trait] +//! trait Test { +//! async fn test(not_okay: Elided, okay: &usize) {} +//! } +//! ``` +//! +//! ```text +//! error[E0726]: implicit elided lifetime not allowed here +//! --> src/main.rs:9:29 +//! | +//! 9 | async fn test(not_okay: Elided, okay: &usize) {} +//! | ^^^^^^- help: indicate the anonymous lifetime: `<'_>` +//! ``` +//! +//! The fix is to name the lifetime or use `'_`. +//! +//! ``` +//! # use async_trait::async_trait; +//! # +//! # type Elided<'a> = &'a usize; +//! # +//! #[async_trait] +//! trait Test { +//! // either +//! async fn test<'e>(elided: Elided<'e>) {} +//! # } +//! # #[async_trait] +//! # trait Test2 { +//! // or +//! async fn test(elided: Elided<'_>) {} +//! } +//! ``` +//! +//!

+//! +//! # Dyn traits +//! +//! Traits with async methods can be used as trait objects as long as they meet +//! the usual requirements for dyn -- no methods with type parameters, no self +//! by value, no associated types, etc. +//! +//! ``` +//! # use async_trait::async_trait; +//! # +//! #[async_trait] +//! pub trait ObjectSafe { +//! async fn f(&self); +//! async fn g(&mut self); +//! } +//! +//! # const IGNORE: &str = stringify! { +//! impl ObjectSafe for MyType {...} +//! +//! let value: MyType = ...; +//! # }; +//! # +//! # struct MyType; +//! # +//! # #[async_trait] +//! # impl ObjectSafe for MyType { +//! # async fn f(&self) {} +//! # async fn g(&mut self) {} +//! # } +//! # +//! # let value: MyType = MyType; +//! let object = &value as &dyn ObjectSafe; // make trait object +//! ``` +//! +//! The one wrinkle is in traits that provide default implementations of async +//! methods. In order for the default implementation to produce a future that is +//! Send, the async_trait macro must emit a bound of `Self: Sync` on trait +//! methods that take `&self` and a bound `Self: Send` on trait methods that +//! take `&mut self`. An example of the former is visible in the expanded code +//! in the explanation section above. +//! +//! If you make a trait with async methods that have default implementations, +//! everything will work except that the trait cannot be used as a trait object. +//! Creating a value of type `&dyn Trait` will produce an error that looks like +//! this: +//! +//! ```text +//! error: the trait `Test` cannot be made into an object +//! --> src/main.rs:8:5 +//! | +//! 8 | async fn cannot_dyn(&self) {} +//! | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//! ``` +//! +//! For traits that need to be object safe and need to have default +//! implementations for some async methods, there are two resolutions. Either +//! you can add Send and/or Sync as supertraits (Send if there are `&mut self` +//! methods with default implementations, Sync if there are `&self` methods with +//! default implementions) to constrain all implementors of the trait such that +//! the default implementations are applicable to them: +//! +//! ``` +//! # use async_trait::async_trait; +//! # +//! #[async_trait] +//! pub trait ObjectSafe: Sync { // added supertrait +//! async fn can_dyn(&self) {} +//! } +//! # +//! # struct MyType; +//! # +//! # #[async_trait] +//! # impl ObjectSafe for MyType {} +//! # +//! # let value = MyType; +//! +//! let object = &value as &dyn ObjectSafe; +//! ``` +//! +//! or you can strike the problematic methods from your trait object by +//! bounding them with `Self: Sized`: +//! +//! ``` +//! # use async_trait::async_trait; +//! # +//! #[async_trait] +//! pub trait ObjectSafe { +//! async fn cannot_dyn(&self) where Self: Sized {} +//! +//! // presumably other methods +//! } +//! # +//! # struct MyType; +//! # +//! # #[async_trait] +//! # impl ObjectSafe for MyType {} +//! # +//! # let value = MyType; +//! +//! let object = &value as &dyn ObjectSafe; +//! ``` + +#![allow(clippy::match_like_matches_macro)] // matches! requires Rust 1.42 + +extern crate proc_macro; + +mod args; +mod expand; +mod lifetime; +mod parse; +mod receiver; +mod respan; + +use crate::args::Args; +use crate::expand::expand; +use crate::parse::Item; +use proc_macro::TokenStream; +use quote::quote; +use syn::parse_macro_input; + +#[proc_macro_attribute] +pub fn async_trait(args: TokenStream, input: TokenStream) -> TokenStream { + let args = parse_macro_input!(args as Args); + let mut item = parse_macro_input!(input as Item); + expand(&mut item, args.local); + TokenStream::from(quote!(#item)) +} diff --git a/third_party/rust/async-trait/src/lifetime.rs b/third_party/rust/async-trait/src/lifetime.rs new file mode 100644 index 000000000000..9d2066be0576 --- /dev/null +++ b/third_party/rust/async-trait/src/lifetime.rs @@ -0,0 +1,80 @@ +use proc_macro2::Span; +use syn::visit_mut::{self, VisitMut}; +use syn::{Block, GenericArgument, Item, Lifetime, Receiver, Signature, TypeReference}; + +pub fn has_async_lifetime(sig: &mut Signature, block: &mut Block) -> bool { + let mut visitor = HasAsyncLifetime(false); + visitor.visit_signature_mut(sig); + visitor.visit_block_mut(block); + visitor.0 +} + +struct HasAsyncLifetime(bool); + +impl VisitMut for HasAsyncLifetime { + fn visit_lifetime_mut(&mut self, life: &mut Lifetime) { + self.0 |= life.to_string() == "'async_trait"; + } + + fn visit_item_mut(&mut self, _: &mut Item) { + // Do not recurse into nested items. + } +} + +pub struct CollectLifetimes { + pub elided: Vec, + pub explicit: Vec, + pub name: &'static str, +} + +impl CollectLifetimes { + pub fn new(name: &'static str) -> Self { + CollectLifetimes { + elided: Vec::new(), + explicit: Vec::new(), + name, + } + } + + fn visit_opt_lifetime(&mut self, lifetime: &mut Option) { + match lifetime { + None => *lifetime = Some(self.next_lifetime()), + Some(lifetime) => self.visit_lifetime(lifetime), + } + } + + fn visit_lifetime(&mut self, lifetime: &mut Lifetime) { + if lifetime.ident == "_" { + *lifetime = self.next_lifetime(); + } else { + self.explicit.push(lifetime.clone()); + } + } + + fn next_lifetime(&mut self) -> Lifetime { + let name = format!("{}{}", self.name, self.elided.len()); + let life = Lifetime::new(&name, Span::call_site()); + self.elided.push(life.clone()); + life + } +} + +impl VisitMut for CollectLifetimes { + fn visit_receiver_mut(&mut self, arg: &mut Receiver) { + if let Some((_, lifetime)) = &mut arg.reference { + self.visit_opt_lifetime(lifetime); + } + } + + fn visit_type_reference_mut(&mut self, ty: &mut TypeReference) { + self.visit_opt_lifetime(&mut ty.lifetime); + visit_mut::visit_type_reference_mut(self, ty); + } + + fn visit_generic_argument_mut(&mut self, gen: &mut GenericArgument) { + if let GenericArgument::Lifetime(lifetime) = gen { + self.visit_lifetime(lifetime); + } + visit_mut::visit_generic_argument_mut(self, gen); + } +} diff --git a/third_party/rust/async-trait/src/parse.rs b/third_party/rust/async-trait/src/parse.rs new file mode 100644 index 000000000000..ebd253514f84 --- /dev/null +++ b/third_party/rust/async-trait/src/parse.rs @@ -0,0 +1,34 @@ +use proc_macro2::Span; +use syn::parse::{Error, Parse, ParseStream, Result}; +use syn::{Attribute, ItemImpl, ItemTrait, Token}; + +pub enum Item { + Trait(ItemTrait), + Impl(ItemImpl), +} + +impl Parse for Item { + fn parse(input: ParseStream) -> Result { + let attrs = input.call(Attribute::parse_outer)?; + let mut lookahead = input.lookahead1(); + if lookahead.peek(Token![unsafe]) { + let ahead = input.fork(); + ahead.parse::()?; + lookahead = ahead.lookahead1(); + } + if lookahead.peek(Token![pub]) || lookahead.peek(Token![trait]) { + let mut item: ItemTrait = input.parse()?; + item.attrs = attrs; + Ok(Item::Trait(item)) + } else if lookahead.peek(Token![impl]) { + let mut item: ItemImpl = input.parse()?; + if item.trait_.is_none() { + return Err(Error::new(Span::call_site(), "expected a trait impl")); + } + item.attrs = attrs; + Ok(Item::Impl(item)) + } else { + Err(lookahead.error()) + } + } +} diff --git a/third_party/rust/async-trait/src/receiver.rs b/third_party/rust/async-trait/src/receiver.rs new file mode 100644 index 000000000000..42733596fcbc --- /dev/null +++ b/third_party/rust/async-trait/src/receiver.rs @@ -0,0 +1,321 @@ +use crate::respan::respan; +use proc_macro2::{Group, Spacing, Span, TokenStream, TokenTree}; +use quote::{quote, quote_spanned}; +use std::iter::FromIterator; +use std::mem; +use syn::punctuated::Punctuated; +use syn::visit_mut::{self, VisitMut}; +use syn::{ + parse_quote, Block, Error, ExprPath, ExprStruct, Ident, Item, Macro, PatPath, PatStruct, + PatTupleStruct, Path, PathArguments, QSelf, Receiver, Signature, Token, Type, TypePath, + WherePredicate, +}; + +pub fn has_self_in_sig(sig: &mut Signature) -> bool { + let mut visitor = HasSelf(false); + visitor.visit_signature_mut(sig); + visitor.0 +} + +pub fn has_self_in_where_predicate(where_predicate: &mut WherePredicate) -> bool { + let mut visitor = HasSelf(false); + visitor.visit_where_predicate_mut(where_predicate); + visitor.0 +} + +pub fn has_self_in_block(block: &mut Block) -> bool { + let mut visitor = HasSelf(false); + visitor.visit_block_mut(block); + visitor.0 +} + +fn has_self_in_token_stream(tokens: TokenStream) -> bool { + tokens.into_iter().any(|tt| match tt { + TokenTree::Ident(ident) => ident == "Self", + TokenTree::Group(group) => has_self_in_token_stream(group.stream()), + _ => false, + }) +} + +struct HasSelf(bool); + +impl VisitMut for HasSelf { + fn visit_expr_path_mut(&mut self, expr: &mut ExprPath) { + self.0 |= expr.path.segments[0].ident == "Self"; + visit_mut::visit_expr_path_mut(self, expr); + } + + fn visit_pat_path_mut(&mut self, pat: &mut PatPath) { + self.0 |= pat.path.segments[0].ident == "Self"; + visit_mut::visit_pat_path_mut(self, pat); + } + + fn visit_type_path_mut(&mut self, ty: &mut TypePath) { + self.0 |= ty.path.segments[0].ident == "Self"; + visit_mut::visit_type_path_mut(self, ty); + } + + fn visit_receiver_mut(&mut self, _arg: &mut Receiver) { + self.0 = true; + } + + fn visit_item_mut(&mut self, _: &mut Item) { + // Do not recurse into nested items. + } + + fn visit_macro_mut(&mut self, mac: &mut Macro) { + if !contains_fn(mac.tokens.clone()) { + self.0 |= has_self_in_token_stream(mac.tokens.clone()); + } + } +} + +pub struct ReplaceReceiver { + pub with: Type, + pub as_trait: Option, +} + +impl ReplaceReceiver { + pub fn with(ty: Type) -> Self { + ReplaceReceiver { + with: ty, + as_trait: None, + } + } + + pub fn with_as_trait(ty: Type, as_trait: Path) -> Self { + ReplaceReceiver { + with: ty, + as_trait: Some(as_trait), + } + } + + fn self_ty(&self, span: Span) -> Type { + respan(&self.with, span) + } + + fn self_to_qself_type(&self, qself: &mut Option, path: &mut Path) { + let include_as_trait = true; + self.self_to_qself(qself, path, include_as_trait); + } + + fn self_to_qself_expr(&self, qself: &mut Option, path: &mut Path) { + let include_as_trait = false; + self.self_to_qself(qself, path, include_as_trait); + } + + fn self_to_qself(&self, qself: &mut Option, path: &mut Path, include_as_trait: bool) { + if path.leading_colon.is_some() { + return; + } + + let first = &path.segments[0]; + if first.ident != "Self" || !first.arguments.is_empty() { + return; + } + + if path.segments.len() == 1 { + self.self_to_expr_path(path); + return; + } + + let span = first.ident.span(); + *qself = Some(QSelf { + lt_token: Token![<](span), + ty: Box::new(self.self_ty(span)), + position: 0, + as_token: None, + gt_token: Token![>](span), + }); + + if include_as_trait && self.as_trait.is_some() { + let as_trait = self.as_trait.as_ref().unwrap().clone(); + path.leading_colon = as_trait.leading_colon; + qself.as_mut().unwrap().position = as_trait.segments.len(); + + let segments = mem::replace(&mut path.segments, as_trait.segments); + path.segments.push_punct(Default::default()); + path.segments.extend(segments.into_pairs().skip(1)); + } else { + path.leading_colon = Some(**path.segments.pairs().next().unwrap().punct().unwrap()); + + let segments = mem::replace(&mut path.segments, Punctuated::new()); + path.segments = segments.into_pairs().skip(1).collect(); + } + } + + fn self_to_expr_path(&self, path: &mut Path) { + if path.leading_colon.is_some() { + return; + } + + let first = &path.segments[0]; + if first.ident != "Self" || !first.arguments.is_empty() { + return; + } + + if let Type::Path(self_ty) = self.self_ty(first.ident.span()) { + let variant = mem::replace(path, self_ty.path); + for segment in &mut path.segments { + if let PathArguments::AngleBracketed(bracketed) = &mut segment.arguments { + if bracketed.colon2_token.is_none() && !bracketed.args.is_empty() { + bracketed.colon2_token = Some(Default::default()); + } + } + } + if variant.segments.len() > 1 { + path.segments.push_punct(Default::default()); + path.segments.extend(variant.segments.into_pairs().skip(1)); + } + } else { + let span = path.segments[0].ident.span(); + let msg = "Self type of this impl is unsupported in expression position"; + let error = Error::new(span, msg).to_compile_error(); + *path = parse_quote!(::core::marker::PhantomData::<#error>); + } + } + + fn visit_token_stream(&self, tokens: &mut TokenStream) -> bool { + let mut out = Vec::new(); + let mut modified = false; + let mut iter = tokens.clone().into_iter().peekable(); + while let Some(tt) = iter.next() { + match tt { + TokenTree::Ident(mut ident) => { + modified |= prepend_underscore_to_self(&mut ident); + if ident == "Self" { + modified = true; + if self.as_trait.is_none() { + let ident = Ident::new("AsyncTrait", ident.span()); + out.push(TokenTree::Ident(ident)); + } else { + let self_ty = self.self_ty(ident.span()); + match iter.peek() { + Some(TokenTree::Punct(p)) + if p.as_char() == ':' && p.spacing() == Spacing::Joint => + { + let next = iter.next().unwrap(); + match iter.peek() { + Some(TokenTree::Punct(p)) if p.as_char() == ':' => { + let span = ident.span(); + out.extend(quote_spanned!(span=> <#self_ty>)); + } + _ => out.extend(quote!(#self_ty)), + } + out.push(next); + } + _ => out.extend(quote!(#self_ty)), + } + } + } else { + out.push(TokenTree::Ident(ident)); + } + } + TokenTree::Group(group) => { + let mut content = group.stream(); + modified |= self.visit_token_stream(&mut content); + let mut new = Group::new(group.delimiter(), content); + new.set_span(group.span()); + out.push(TokenTree::Group(new)); + } + other => out.push(other), + } + } + if modified { + *tokens = TokenStream::from_iter(out); + } + modified + } +} + +impl VisitMut for ReplaceReceiver { + // `Self` -> `Receiver` + fn visit_type_mut(&mut self, ty: &mut Type) { + if let Type::Path(node) = ty { + if node.qself.is_none() && node.path.is_ident("Self") { + *ty = self.self_ty(node.path.segments[0].ident.span()); + } else { + self.visit_type_path_mut(node); + } + } else { + visit_mut::visit_type_mut(self, ty); + } + } + + // `Self::Assoc` -> `::Assoc` + fn visit_type_path_mut(&mut self, ty: &mut TypePath) { + if ty.qself.is_none() { + self.self_to_qself_type(&mut ty.qself, &mut ty.path); + } + visit_mut::visit_type_path_mut(self, ty); + } + + // `Self::method` -> `::method` + fn visit_expr_path_mut(&mut self, expr: &mut ExprPath) { + if expr.qself.is_none() { + prepend_underscore_to_self(&mut expr.path.segments[0].ident); + self.self_to_qself_expr(&mut expr.qself, &mut expr.path); + } + visit_mut::visit_expr_path_mut(self, expr); + } + + fn visit_expr_struct_mut(&mut self, expr: &mut ExprStruct) { + self.self_to_expr_path(&mut expr.path); + visit_mut::visit_expr_struct_mut(self, expr); + } + + fn visit_pat_path_mut(&mut self, pat: &mut PatPath) { + if pat.qself.is_none() { + self.self_to_qself_expr(&mut pat.qself, &mut pat.path); + } + visit_mut::visit_pat_path_mut(self, pat); + } + + fn visit_pat_struct_mut(&mut self, pat: &mut PatStruct) { + self.self_to_expr_path(&mut pat.path); + visit_mut::visit_pat_struct_mut(self, pat); + } + + fn visit_pat_tuple_struct_mut(&mut self, pat: &mut PatTupleStruct) { + self.self_to_expr_path(&mut pat.path); + visit_mut::visit_pat_tuple_struct_mut(self, pat); + } + + fn visit_item_mut(&mut self, i: &mut Item) { + match i { + // Visit `macro_rules!` because locally defined macros can refer to `self`. + Item::Macro(i) if i.mac.path.is_ident("macro_rules") => { + self.visit_macro_mut(&mut i.mac) + } + // Otherwise, do not recurse into nested items. + _ => {} + } + } + + fn visit_macro_mut(&mut self, mac: &mut Macro) { + // We can't tell in general whether `self` inside a macro invocation + // refers to the self in the argument list or a different self + // introduced within the macro. Heuristic: if the macro input contains + // `fn`, then `self` is more likely to refer to something other than the + // outer function's self argument. + if !contains_fn(mac.tokens.clone()) { + self.visit_token_stream(&mut mac.tokens); + } + } +} + +fn contains_fn(tokens: TokenStream) -> bool { + tokens.into_iter().any(|tt| match tt { + TokenTree::Ident(ident) => ident == "fn", + TokenTree::Group(group) => contains_fn(group.stream()), + _ => false, + }) +} + +fn prepend_underscore_to_self(ident: &mut Ident) -> bool { + let modified = ident == "self"; + if modified { + *ident = Ident::new("_self", ident.span()); + } + modified +} diff --git a/third_party/rust/async-trait/src/respan.rs b/third_party/rust/async-trait/src/respan.rs new file mode 100644 index 000000000000..38f6612c41a3 --- /dev/null +++ b/third_party/rust/async-trait/src/respan.rs @@ -0,0 +1,22 @@ +use proc_macro2::{Span, TokenStream}; +use quote::ToTokens; +use syn::parse::Parse; + +pub(crate) fn respan(node: &T, span: Span) -> T +where + T: ToTokens + Parse, +{ + let tokens = node.to_token_stream(); + let respanned = respan_tokens(tokens, span); + syn::parse2(respanned).unwrap() +} + +fn respan_tokens(tokens: TokenStream, span: Span) -> TokenStream { + tokens + .into_iter() + .map(|mut token| { + token.set_span(span); + token + }) + .collect() +} diff --git a/third_party/rust/async-trait/tests/compiletest.rs b/third_party/rust/async-trait/tests/compiletest.rs new file mode 100644 index 000000000000..f9aea23b5154 --- /dev/null +++ b/third_party/rust/async-trait/tests/compiletest.rs @@ -0,0 +1,6 @@ +#[rustversion::attr(not(nightly), ignore)] +#[test] +fn ui() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/ui/*.rs"); +} diff --git a/third_party/rust/async-trait/tests/executor/mod.rs b/third_party/rust/async-trait/tests/executor/mod.rs new file mode 100644 index 000000000000..f48b3480bbd5 --- /dev/null +++ b/third_party/rust/async-trait/tests/executor/mod.rs @@ -0,0 +1,35 @@ +use std::future::Future; +use std::pin::Pin; +use std::ptr; +use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; + +// Executor for a future that resolves immediately (test only). +pub fn block_on_simple(mut fut: F) -> F::Output { + unsafe fn clone(_null: *const ()) -> RawWaker { + unimplemented!() + } + + unsafe fn wake(_null: *const ()) { + unimplemented!() + } + + unsafe fn wake_by_ref(_null: *const ()) { + unimplemented!() + } + + unsafe fn drop(_null: *const ()) {} + + let data = ptr::null(); + let vtable = &RawWakerVTable::new(clone, wake, wake_by_ref, drop); + let raw_waker = RawWaker::new(data, vtable); + let waker = unsafe { Waker::from_raw(raw_waker) }; + let mut cx = Context::from_waker(&waker); + + // fut does not move until it gets dropped. + let fut = unsafe { Pin::new_unchecked(&mut fut) }; + + match fut.poll(&mut cx) { + Poll::Ready(output) => output, + Poll::Pending => panic!("future did not resolve immediately"), + } +} diff --git a/third_party/rust/async-trait/tests/test.rs b/third_party/rust/async-trait/tests/test.rs new file mode 100644 index 000000000000..5fc238ba456f --- /dev/null +++ b/third_party/rust/async-trait/tests/test.rs @@ -0,0 +1,1079 @@ +#![cfg_attr( + async_trait_nightly_testing, + feature(min_specialization, min_const_generics) +)] + +use async_trait::async_trait; + +pub mod executor; + +// Dummy module to check that the expansion refer to rust's core crate +mod core {} + +#[async_trait] +trait Trait { + type Assoc; + + async fn selfvalue(self) + where + Self: Sized, + { + } + + async fn selfref(&self) {} + + async fn selfmut(&mut self) {} + + async fn required() -> Self::Assoc; + + async fn elided_lifetime(_x: &str) {} + + async fn explicit_lifetime<'a>(_x: &'a str) {} + + async fn generic_type_param(x: Box) -> T { + *x + } + + async fn calls(&self) { + self.selfref().await; + Self::elided_lifetime("").await; + ::elided_lifetime("").await; + } + + async fn calls_mut(&mut self) { + self.selfmut().await; + } +} + +struct Struct; + +#[async_trait] +impl Trait for Struct { + type Assoc = (); + + async fn selfvalue(self) {} + + async fn selfref(&self) {} + + async fn selfmut(&mut self) {} + + async fn required() -> Self::Assoc {} + + async fn elided_lifetime(_x: &str) {} + + async fn explicit_lifetime<'a>(_x: &'a str) {} + + async fn generic_type_param(x: Box) -> T { + *x + } + + async fn calls(&self) { + self.selfref().await; + Self::elided_lifetime("").await; + ::elided_lifetime("").await; + } + + async fn calls_mut(&mut self) { + self.selfmut().await; + } +} + +pub async fn test() { + let mut s = Struct; + s.selfref().await; + s.selfmut().await; + s.selfvalue().await; + + Struct::required().await; + Struct::elided_lifetime("").await; + Struct::explicit_lifetime("").await; + Struct::generic_type_param(Box::new("")).await; + + let mut s = Struct; + s.calls().await; + s.calls_mut().await; +} + +pub async fn test_object_safe_without_default() { + #[async_trait] + trait ObjectSafe { + async fn f(&self); + } + + #[async_trait] + impl ObjectSafe for Struct { + async fn f(&self) {} + } + + let object = &Struct as &dyn ObjectSafe; + object.f().await; +} + +pub async fn test_object_safe_with_default() { + #[async_trait] + trait ObjectSafe: Sync { + async fn f(&self) {} + } + + #[async_trait] + impl ObjectSafe for Struct { + async fn f(&self) {} + } + + let object = &Struct as &dyn ObjectSafe; + object.f().await; +} + +pub async fn test_object_no_send() { + #[async_trait(?Send)] + trait ObjectSafe: Sync { + async fn f(&self) {} + } + + #[async_trait(?Send)] + impl ObjectSafe for Struct { + async fn f(&self) {} + } + + let object = &Struct as &dyn ObjectSafe; + object.f().await; +} + +#[async_trait] +pub unsafe trait UnsafeTrait {} + +#[async_trait] +unsafe impl UnsafeTrait for () {} + +#[async_trait] +pub(crate) unsafe trait UnsafeTraitPubCrate {} + +#[async_trait] +unsafe trait UnsafeTraitPrivate {} + +// https://github.com/dtolnay/async-trait/issues/1 +pub mod issue1 { + use async_trait::async_trait; + + #[async_trait] + trait Issue1 { + async fn f(&self); + } + + #[async_trait] + impl Issue1 for Vec { + async fn f(&self) {} + } +} + +// https://github.com/dtolnay/async-trait/issues/2 +pub mod issue2 { + use async_trait::async_trait; + use std::future::Future; + + #[async_trait] + pub trait Issue2: Future { + async fn flatten(self) -> ::Output + where + Self::Output: Future + Send, + Self: Sized, + { + let nested_future = self.await; + nested_future.await + } + } +} + +// https://github.com/dtolnay/async-trait/issues/9 +pub mod issue9 { + use async_trait::async_trait; + + #[async_trait] + pub trait Issue9: Sized + Send { + async fn f(_x: Self) {} + } +} + +// https://github.com/dtolnay/async-trait/issues/11 +pub mod issue11 { + use async_trait::async_trait; + use std::sync::Arc; + + #[async_trait] + trait Issue11 { + async fn example(self: Arc); + } + + struct Struct; + + #[async_trait] + impl Issue11 for Struct { + async fn example(self: Arc) {} + } +} + +// https://github.com/dtolnay/async-trait/issues/15 +pub mod issue15 { + use async_trait::async_trait; + use std::marker::PhantomData; + + trait Trait {} + + #[async_trait] + trait Issue15 { + async fn myfn(&self, _: PhantomData) {} + } +} + +// https://github.com/dtolnay/async-trait/issues/17 +pub mod issue17 { + use async_trait::async_trait; + + #[async_trait] + trait Issue17 { + async fn f(&self); + } + + struct Struct { + string: String, + } + + #[async_trait] + impl Issue17 for Struct { + async fn f(&self) { + println!("{}", self.string); + } + } +} + +// https://github.com/dtolnay/async-trait/issues/23 +pub mod issue23 { + use async_trait::async_trait; + + #[async_trait] + pub trait Issue23 { + async fn f(self); + + async fn g(mut self) + where + Self: Sized, + { + do_something(&mut self); + } + } + + struct S {} + + #[async_trait] + impl Issue23 for S { + async fn f(mut self) { + do_something(&mut self); + } + } + + fn do_something(_: &mut T) {} +} + +// https://github.com/dtolnay/async-trait/issues/25 +#[cfg(async_trait_nightly_testing)] +pub mod issue25 { + use crate::executor; + use async_trait::async_trait; + use std::fmt::{Display, Write}; + + #[async_trait] + trait AsyncToString { + async fn async_to_string(&self) -> String; + } + + #[async_trait] + impl AsyncToString for String { + async fn async_to_string(&self) -> String { + "special".to_owned() + } + } + + macro_rules! hide_from_stable_parser { + ($($tt:tt)*) => { + $($tt)* + }; + } + + hide_from_stable_parser! { + #[async_trait] + impl AsyncToString for T { + default async fn async_to_string(&self) -> String { + let mut buf = String::new(); + buf.write_fmt(format_args!("{}", self)).unwrap(); + buf + } + } + } + + #[test] + fn test() { + let fut = true.async_to_string(); + assert_eq!(executor::block_on_simple(fut), "true"); + + let string = String::new(); + let fut = string.async_to_string(); + assert_eq!(executor::block_on_simple(fut), "special"); + } +} + +// https://github.com/dtolnay/async-trait/issues/28 +pub mod issue28 { + use async_trait::async_trait; + + struct Str<'a>(&'a str); + + #[async_trait] + trait Trait1<'a> { + async fn f(x: Str<'a>) -> &'a str; + async fn g(x: Str<'a>) -> &'a str { + x.0 + } + } + + #[async_trait] + impl<'a> Trait1<'a> for str { + async fn f(x: Str<'a>) -> &'a str { + x.0 + } + } + + #[async_trait] + trait Trait2 { + async fn f(); + } + + #[async_trait] + impl<'a> Trait2 for &'a () { + async fn f() {} + } + + #[async_trait] + trait Trait3<'a, 'b> { + async fn f(_: &'a &'b ()); // chain 'a and 'b + async fn g(_: &'b ()); // chain 'b only + async fn h(); // do not chain + } +} + +// https://github.com/dtolnay/async-trait/issues/31 +pub mod issue31 { + use async_trait::async_trait; + + pub struct Struct<'a> { + pub name: &'a str, + } + + #[async_trait] + pub trait Trait<'a> { + async fn hello(thing: Struct<'a>) -> String; + async fn hello_twice(one: Struct<'a>, two: Struct<'a>) -> String { + let str1 = Self::hello(one).await; + let str2 = Self::hello(two).await; + str1 + &str2 + } + } +} + +// https://github.com/dtolnay/async-trait/issues/42 +pub mod issue42 { + use async_trait::async_trait; + + #[async_trait] + pub trait Context: Sized { + async fn from_parts() -> Self; + } + + pub struct TokenContext; + + #[async_trait] + impl Context for TokenContext { + async fn from_parts() -> TokenContext { + TokenContext + } + } +} + +// https://github.com/dtolnay/async-trait/issues/44 +pub mod issue44 { + use async_trait::async_trait; + + #[async_trait] + pub trait StaticWithWhereSelf + where + Box: Sized, + Self: Sized + Send, + { + async fn get_one() -> u8 { + 1 + } + } + + pub struct Struct; + + #[async_trait] + impl StaticWithWhereSelf for Struct {} +} + +// https://github.com/dtolnay/async-trait/issues/45 +pub mod issue45 { + use crate::executor; + use async_trait::async_trait; + use std::fmt::Debug; + use std::sync::atomic::{AtomicU64, Ordering}; + use std::sync::{Arc, Mutex}; + use tracing::event::Event; + use tracing::field::{Field, Visit}; + use tracing::span::{Attributes, Id, Record}; + use tracing::{info, instrument, subscriber, Metadata, Subscriber}; + + #[async_trait] + pub trait Parent { + async fn foo(&mut self, v: usize); + } + + #[async_trait] + pub trait Child { + async fn bar(&self); + } + + #[derive(Debug)] + struct Impl(usize); + + #[async_trait] + impl Parent for Impl { + #[instrument] + async fn foo(&mut self, v: usize) { + self.0 = v; + self.bar().await; + } + } + + #[async_trait] + impl Child for Impl { + // Let's check that tracing detects the renaming of the `self` variable + // too, as tracing::instrument is not going to be able to skip the + // `self` argument if it can't find it in the function signature. + #[instrument(skip(self))] + async fn bar(&self) { + info!(val = self.0); + } + } + + // A simple subscriber implementation to test the behavior of async-trait + // with tokio-rs/tracing. This implementation is not robust against race + // conditions, but it's not an issue here as we are only polling on a single + // future at a time. + #[derive(Debug)] + struct SubscriberInner { + current_depth: AtomicU64, + // We assert that nested functions work. If the fix were to break, we + // would see two top-level functions instead of `bar` nested in `foo`. + max_depth: AtomicU64, + max_span_id: AtomicU64, + // Name of the variable / value / depth when the event was recorded. + value: Mutex>, + } + + #[derive(Debug, Clone)] + struct TestSubscriber { + inner: Arc, + } + + impl TestSubscriber { + fn new() -> Self { + TestSubscriber { + inner: Arc::new(SubscriberInner { + current_depth: AtomicU64::new(0), + max_depth: AtomicU64::new(0), + max_span_id: AtomicU64::new(1), + value: Mutex::new(None), + }), + } + } + } + + struct U64Visitor(Option<(&'static str, u64)>); + + impl Visit for U64Visitor { + fn record_debug(&mut self, _field: &Field, _value: &dyn Debug) {} + + fn record_u64(&mut self, field: &Field, value: u64) { + self.0 = Some((field.name(), value)); + } + } + + impl Subscriber for TestSubscriber { + fn enabled(&self, _metadata: &Metadata) -> bool { + true + } + fn new_span(&self, _span: &Attributes) -> Id { + Id::from_u64(self.inner.max_span_id.fetch_add(1, Ordering::AcqRel)) + } + fn record(&self, _span: &Id, _values: &Record) {} + fn record_follows_from(&self, _span: &Id, _follows: &Id) {} + fn event(&self, event: &Event) { + let mut visitor = U64Visitor(None); + event.record(&mut visitor); + if let Some((s, v)) = visitor.0 { + let current_depth = self.inner.current_depth.load(Ordering::Acquire); + *self.inner.value.lock().unwrap() = Some((s, v, current_depth)); + } + } + fn enter(&self, _span: &Id) { + let old_depth = self.inner.current_depth.fetch_add(1, Ordering::AcqRel); + if old_depth + 1 > self.inner.max_depth.load(Ordering::Acquire) { + self.inner.max_depth.fetch_add(1, Ordering::AcqRel); + } + } + fn exit(&self, _span: &Id) { + self.inner.current_depth.fetch_sub(1, Ordering::AcqRel); + } + } + + #[test] + fn tracing() { + // Create the future outside of the subscriber, as no call to tracing + // should be made until the future is polled. + let mut struct_impl = Impl(0); + let fut = struct_impl.foo(5); + let subscriber = TestSubscriber::new(); + subscriber::with_default(subscriber.clone(), || executor::block_on_simple(fut)); + // Did we enter bar inside of foo? + assert_eq!(subscriber.inner.max_depth.load(Ordering::Acquire), 2); + // Have we exited all spans? + assert_eq!(subscriber.inner.current_depth.load(Ordering::Acquire), 0); + // Did we create only two spans? Note: spans start at 1, hence the -1. + assert_eq!(subscriber.inner.max_span_id.load(Ordering::Acquire) - 1, 2); + // Was the value recorded at the right depth i.e. in the right function? + // If so, was it the expected value? + assert_eq!(*subscriber.inner.value.lock().unwrap(), Some(("val", 5, 2))); + } +} + +// https://github.com/dtolnay/async-trait/issues/46 +pub mod issue46 { + use async_trait::async_trait; + + macro_rules! implement_commands_workaround { + ($tyargs:tt : $ty:tt) => { + #[async_trait] + pub trait AsyncCommands1: Sized { + async fn f<$tyargs: $ty>(&mut self, x: $tyargs) { + self.f(x).await + } + } + }; + } + + implement_commands_workaround!(K: Send); + + macro_rules! implement_commands { + ($tyargs:ident : $ty:ident) => { + #[async_trait] + pub trait AsyncCommands2: Sized { + async fn f<$tyargs: $ty>(&mut self, x: $tyargs) { + self.f(x).await + } + } + }; + } + + implement_commands!(K: Send); +} + +// https://github.com/dtolnay/async-trait/issues/53 +pub mod issue53 { + use async_trait::async_trait; + + pub struct Unit; + pub struct Tuple(u8); + pub struct Struct { + pub x: u8, + } + + #[async_trait] + pub trait Trait { + async fn method(); + } + + #[async_trait] + impl Trait for Unit { + async fn method() { + let _ = Self; + } + } + + #[async_trait] + impl Trait for Tuple { + async fn method() { + let _ = Self(0); + } + } + + #[async_trait] + impl Trait for Struct { + async fn method() { + let _ = Self { x: 0 }; + } + } + + #[async_trait] + impl Trait for std::marker::PhantomData { + async fn method() { + let _ = Self; + } + } +} + +// https://github.com/dtolnay/async-trait/issues/57 +#[cfg(async_trait_nightly_testing)] +pub mod issue57 { + use crate::executor; + use async_trait::async_trait; + + #[async_trait] + trait Trait { + async fn const_generic(_: [T; C]) {} + } + + struct Struct; + + #[async_trait] + impl Trait for Struct { + async fn const_generic(_: [T; C]) {} + } + + #[test] + fn test() { + let fut = Struct::const_generic([0; 10]); + executor::block_on_simple(fut); + } +} + +// https://github.com/dtolnay/async-trait/issues/68 +pub mod issue68 { + #[rustversion::since(1.40)] // procedural macros cannot expand to macro definitions in 1.39. + #[async_trait::async_trait] + pub trait Example { + async fn method(&self) { + macro_rules! t { + () => {{ + let _: &Self = self; + }}; + } + t!(); + } + } +} + +// https://github.com/dtolnay/async-trait/issues/73 +pub mod issue73 { + use async_trait::async_trait; + + #[async_trait] + pub trait Example { + const ASSOCIATED: &'static str; + + async fn associated(&self) { + println!("Associated:{}", Self::ASSOCIATED); + } + } +} + +// https://github.com/dtolnay/async-trait/issues/81 +pub mod issue81 { + use async_trait::async_trait; + + #[async_trait] + pub trait Trait { + async fn handle(&self); + } + + pub enum Enum { + Variant, + } + + #[async_trait] + impl Trait for Enum { + async fn handle(&self) { + let Enum::Variant = self; + let Self::Variant = self; + } + } +} + +// https://github.com/dtolnay/async-trait/issues/83 +pub mod issue83 { + #![allow(clippy::needless_arbitrary_self_type)] + + use async_trait::async_trait; + + #[async_trait] + pub trait Trait { + async fn f(&self) {} + async fn g(self: &Self) {} + } +} + +// https://github.com/dtolnay/async-trait/issues/85 +pub mod issue85 { + #![deny(non_snake_case)] + + use async_trait::async_trait; + + #[async_trait] + pub trait Trait { + #[allow(non_snake_case)] + async fn camelCase(); + } + + pub struct Struct; + + #[async_trait] + impl Trait for Struct { + async fn camelCase() {} + } +} + +// https://github.com/dtolnay/async-trait/issues/87 +pub mod issue87 { + use async_trait::async_trait; + + #[async_trait] + pub trait Trait { + async fn f(&self); + } + + pub enum Tuple { + V(), + } + + pub enum Struct { + V {}, + } + + #[async_trait] + impl Trait for Tuple { + async fn f(&self) { + let Tuple::V() = self; + let Self::V() = self; + let _ = Self::V; + let _ = Self::V(); + } + } + + #[async_trait] + impl Trait for Struct { + async fn f(&self) { + let Struct::V {} = self; + let Self::V {} = self; + let _ = Self::V {}; + } + } +} + +// https://github.com/dtolnay/async-trait/issues/89 +pub mod issue89 { + #![allow(bare_trait_objects)] + + use async_trait::async_trait; + + #[async_trait] + trait Trait { + async fn f(&self); + } + + #[async_trait] + impl Trait for Send + Sync { + async fn f(&self) {} + } + + #[async_trait] + impl Trait for dyn Fn(i8) + Send + Sync { + async fn f(&self) {} + } + + #[async_trait] + impl Trait for (dyn Fn(u8) + Send + Sync) { + async fn f(&self) {} + } +} + +// https://github.com/dtolnay/async-trait/issues/92 +pub mod issue92 { + use async_trait::async_trait; + + macro_rules! mac { + ($($tt:tt)*) => { + $($tt)* + }; + } + + pub struct Struct { + _x: T, + } + + impl Struct { + const ASSOCIATED1: &'static str = "1"; + async fn associated1() {} + } + + #[async_trait] + pub trait Trait + where + mac!(Self): Send, + { + const ASSOCIATED2: &'static str; + type Associated2; + + #[allow(path_statements, clippy::no_effect)] + async fn associated2(&self) { + // trait items + mac!(let _: Self::Associated2;); + mac!(let _: ::Associated2;); + mac!(let _: ::Associated2;); + mac!(Self::ASSOCIATED2;); + mac!(::ASSOCIATED2;); + mac!(::ASSOCIATED2;); + mac!(let _ = Self::associated2(self);); + mac!(let _ = ::associated2(self);); + mac!(let _ = ::associated2(self);); + } + } + + #[async_trait] + impl Trait for Struct + where + mac!(Self): Send, + { + const ASSOCIATED2: &'static str = "2"; + type Associated2 = (); + + #[allow(path_statements, clippy::no_effect)] + async fn associated2(&self) { + // inherent items + mac!(Self::ASSOCIATED1;); + mac!(::ASSOCIATED1;); + mac!(let _ = Self::associated1();); + mac!(let _ = ::associated1();); + + // trait items + mac!(let _: ::Associated2;); + mac!(Self::ASSOCIATED2;); + mac!(::ASSOCIATED2;); + mac!(::ASSOCIATED2;); + mac!(let _ = Self::associated2(self);); + mac!(let _ = ::associated2(self);); + mac!(let _ = ::associated2(self);); + } + } + + pub struct Unit; + + #[async_trait] + impl Trait for Unit { + const ASSOCIATED2: &'static str = "2"; + type Associated2 = (); + + async fn associated2(&self) { + mac!(let Self: Self = *self;); + } + } +} + +// https://github.com/dtolnay/async-trait/issues/92#issuecomment-683370136 +pub mod issue92_2 { + use async_trait::async_trait; + + macro_rules! mac { + ($($tt:tt)*) => { + $($tt)* + }; + } + + pub trait Trait1 { + fn func1(); + } + + #[async_trait] + pub trait Trait2: Trait1 { + async fn func2() { + mac!(Self::func1()); + + macro_rules! mac2 { + ($($tt:tt)*) => { + Self::func1(); + }; + } + mac2!(); + } + } +} + +// https://github.com/dtolnay/async-trait/issues/104 +pub mod issue104 { + use async_trait::async_trait; + + #[async_trait] + trait T1 { + async fn id(&self) -> i32; + } + + macro_rules! impl_t1 { + ($ty:ty, $id:expr) => { + #[async_trait] + impl T1 for $ty { + async fn id(&self) -> i32 { + $id + } + } + }; + } + + struct Foo; + + impl_t1!(Foo, 1); +} + +// https://github.com/dtolnay/async-trait/issues/106 +pub mod issue106 { + use async_trait::async_trait; + use std::future::Future; + + #[async_trait] + pub trait ProcessPool: Send + Sync { + type ThreadPool; + + async fn spawn(&self, work: F) -> T + where + F: FnOnce(&Self::ThreadPool) -> Fut + Send, + Fut: Future + 'static; + } + + #[async_trait] + impl ProcessPool for &P + where + P: ProcessPool, + { + type ThreadPool = P::ThreadPool; + + async fn spawn(&self, work: F) -> T + where + F: FnOnce(&Self::ThreadPool) -> Fut + Send, + Fut: Future + 'static, + { + (**self).spawn(work).await + } + } +} + +// https://github.com/dtolnay/async-trait/issues/110 +pub mod issue110 { + #![deny(clippy::all)] + + use async_trait::async_trait; + use std::marker::PhantomData; + + #[async_trait] + pub trait Loader { + async fn load(&self, key: &str); + } + + pub struct AwsEc2MetadataLoader<'a> { + marker: PhantomData<&'a ()>, + } + + #[async_trait] + impl Loader for AwsEc2MetadataLoader<'_> { + async fn load(&self, _key: &str) {} + } +} + +// https://github.com/dtolnay/async-trait/issues/120 +pub mod issue120 { + #![deny(clippy::trivially_copy_pass_by_ref)] + + use async_trait::async_trait; + + #[async_trait] + trait Trait { + async fn f(&self); + } + + #[async_trait] + impl Trait for () { + async fn f(&self) {} + } +} + +// https://github.com/dtolnay/async-trait/issues/123 +pub mod issue123 { + use async_trait::async_trait; + + #[async_trait] + trait Trait { + async fn f(&self) -> &str + where + T: 'async_trait, + { + "default" + } + } + + #[async_trait] + impl Trait for () {} +} + +// https://github.com/dtolnay/async-trait/issues/129 +pub mod issue129 { + #![deny(clippy::pedantic)] + + use async_trait::async_trait; + + #[async_trait] + pub trait TestTrait { + async fn a(_b: u8, c: u8) -> u8 { + c + } + } + + pub struct TestStruct; + + #[async_trait] + impl TestTrait for TestStruct { + async fn a(_b: u8, c: u8) -> u8 { + c + } + } +} + +// https://github.com/dtolnay/async-trait/issues/134 +#[cfg(async_trait_nightly_testing)] +pub mod issue134 { + use async_trait::async_trait; + + #[async_trait] + trait TestTrait { + async fn run(self) + where + Self: Sized, + { + } + } + + pub struct TestStruct; + + #[async_trait] + impl TestTrait for TestStruct { + async fn run(self) + where + Self: Sized, + { + } + } +} diff --git a/third_party/rust/async-trait/tests/ui/bare-trait-object.rs b/third_party/rust/async-trait/tests/ui/bare-trait-object.rs new file mode 100644 index 000000000000..afcd6b44c333 --- /dev/null +++ b/third_party/rust/async-trait/tests/ui/bare-trait-object.rs @@ -0,0 +1,15 @@ +#![deny(bare_trait_objects)] + +use async_trait::async_trait; + +#[async_trait] +trait Trait { + async fn f(&self); +} + +#[async_trait] +impl Trait for Send + Sync { + async fn f(&self) {} +} + +fn main() {} diff --git a/third_party/rust/async-trait/tests/ui/bare-trait-object.stderr b/third_party/rust/async-trait/tests/ui/bare-trait-object.stderr new file mode 100644 index 000000000000..98cf67919ab7 --- /dev/null +++ b/third_party/rust/async-trait/tests/ui/bare-trait-object.stderr @@ -0,0 +1,11 @@ +error: trait objects without an explicit `dyn` are deprecated + --> $DIR/bare-trait-object.rs:11:16 + | +11 | impl Trait for Send + Sync { + | ^^^^^^^^^^^ help: use `dyn`: `dyn Send + Sync` + | +note: the lint level is defined here + --> $DIR/bare-trait-object.rs:1:9 + | +1 | #![deny(bare_trait_objects)] + | ^^^^^^^^^^^^^^^^^^ diff --git a/third_party/rust/async-trait/tests/ui/delimiter-span.rs b/third_party/rust/async-trait/tests/ui/delimiter-span.rs new file mode 100644 index 000000000000..68456fa1dd94 --- /dev/null +++ b/third_party/rust/async-trait/tests/ui/delimiter-span.rs @@ -0,0 +1,21 @@ +use async_trait::async_trait; + +macro_rules! picky { + (ident) => {}; +} + +#[async_trait] +trait Trait { + async fn method(); +} + +struct Struct; + +#[async_trait] +impl Trait for Struct { + async fn method() { + picky!({ 123 }); + } +} + +fn main() {} diff --git a/third_party/rust/async-trait/tests/ui/delimiter-span.stderr b/third_party/rust/async-trait/tests/ui/delimiter-span.stderr new file mode 100644 index 000000000000..e08044586e2d --- /dev/null +++ b/third_party/rust/async-trait/tests/ui/delimiter-span.stderr @@ -0,0 +1,8 @@ +error: no rules expected the token `{` + --> $DIR/delimiter-span.rs:17:16 + | +3 | macro_rules! picky { + | ------------------ when calling this macro +... +17 | picky!({ 123 }); + | ^ no rules expected this token in macro call diff --git a/third_party/rust/async-trait/tests/ui/missing-body.rs b/third_party/rust/async-trait/tests/ui/missing-body.rs new file mode 100644 index 000000000000..f3e1126d1d5b --- /dev/null +++ b/third_party/rust/async-trait/tests/ui/missing-body.rs @@ -0,0 +1,15 @@ +use async_trait::async_trait; + +#[async_trait] +trait Trait { + async fn f(&self); +} + +struct Thing; + +#[async_trait] +impl Trait for Thing { + async fn f(&self); +} + +fn main() {} diff --git a/third_party/rust/async-trait/tests/ui/missing-body.stderr b/third_party/rust/async-trait/tests/ui/missing-body.stderr new file mode 100644 index 000000000000..2d9b09c77a8e --- /dev/null +++ b/third_party/rust/async-trait/tests/ui/missing-body.stderr @@ -0,0 +1,7 @@ +error: associated function in `impl` without body + --> $DIR/missing-body.rs:12:5 + | +12 | async fn f(&self); + | ^^^^^^^^^^^^^^^^^- + | | + | help: provide a definition for the function: `{ }` diff --git a/third_party/rust/async-trait/tests/ui/must-use.rs b/third_party/rust/async-trait/tests/ui/must-use.rs new file mode 100644 index 000000000000..7ad0d9bf33d9 --- /dev/null +++ b/third_party/rust/async-trait/tests/ui/must-use.rs @@ -0,0 +1,21 @@ +#![deny(unused_must_use)] + +use async_trait::async_trait; + +#[async_trait] +trait Interface { + async fn f(&self); +} + +struct Thing; + +#[async_trait] +impl Interface for Thing { + async fn f(&self) {} +} + +pub async fn f() { + Thing.f(); +} + +fn main() {} diff --git a/third_party/rust/async-trait/tests/ui/must-use.stderr b/third_party/rust/async-trait/tests/ui/must-use.stderr new file mode 100644 index 000000000000..c09a51ebdad5 --- /dev/null +++ b/third_party/rust/async-trait/tests/ui/must-use.stderr @@ -0,0 +1,11 @@ +error: unused return value of `Interface::f` that must be used + --> $DIR/must-use.rs:18:5 + | +18 | Thing.f(); + | ^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/must-use.rs:1:9 + | +1 | #![deny(unused_must_use)] + | ^^^^^^^^^^^^^^^ diff --git a/third_party/rust/async-trait/tests/ui/self-span.rs b/third_party/rust/async-trait/tests/ui/self-span.rs new file mode 100644 index 000000000000..b01f247032d5 --- /dev/null +++ b/third_party/rust/async-trait/tests/ui/self-span.rs @@ -0,0 +1,30 @@ +use async_trait::async_trait; + +pub struct S {} + +pub enum E { + V {}, +} + +#[async_trait] +pub trait Trait { + async fn method(self); +} + +#[async_trait] +impl Trait for S { + async fn method(self) { + let _: () = self; + let _: Self = Self; + } +} + +#[async_trait] +impl Trait for E { + async fn method(self) { + let _: () = self; + let _: Self = Self::V; + } +} + +fn main() {} diff --git a/third_party/rust/async-trait/tests/ui/self-span.stderr b/third_party/rust/async-trait/tests/ui/self-span.stderr new file mode 100644 index 000000000000..fb1152874a19 --- /dev/null +++ b/third_party/rust/async-trait/tests/ui/self-span.stderr @@ -0,0 +1,30 @@ +error[E0423]: expected value, found struct `S` + --> $DIR/self-span.rs:18:23 + | +3 | pub struct S {} + | --------------- `S` defined here +... +18 | let _: Self = Self; + | ^^^^ help: use struct literal syntax instead: `S {}` + +error[E0308]: mismatched types + --> $DIR/self-span.rs:17:21 + | +17 | let _: () = self; + | -- ^^^^ expected `()`, found struct `S` + | | + | expected due to this + +error[E0308]: mismatched types + --> $DIR/self-span.rs:25:21 + | +25 | let _: () = self; + | -- ^^^^ expected `()`, found enum `E` + | | + | expected due to this + +error[E0533]: expected unit struct, unit variant or constant, found struct variant `Self::V` + --> $DIR/self-span.rs:26:23 + | +26 | let _: Self = Self::V; + | ^^^^^^^ diff --git a/third_party/rust/async-trait/tests/ui/send-not-implemented.rs b/third_party/rust/async-trait/tests/ui/send-not-implemented.rs new file mode 100644 index 000000000000..a3e3856aa968 --- /dev/null +++ b/third_party/rust/async-trait/tests/ui/send-not-implemented.rs @@ -0,0 +1,15 @@ +use async_trait::async_trait; +use std::sync::Mutex; + +async fn f() {} + +#[async_trait] +trait Test { + async fn test(&self) { + let mutex = Mutex::new(()); + let _guard = mutex.lock().unwrap(); + f().await; + } +} + +fn main() {} diff --git a/third_party/rust/async-trait/tests/ui/send-not-implemented.stderr b/third_party/rust/async-trait/tests/ui/send-not-implemented.stderr new file mode 100644 index 000000000000..05c445b9e20a --- /dev/null +++ b/third_party/rust/async-trait/tests/ui/send-not-implemented.stderr @@ -0,0 +1,22 @@ +error: future cannot be sent between threads safely + --> $DIR/send-not-implemented.rs:8:26 + | +8 | async fn test(&self) { + | __________________________^ +9 | | let mutex = Mutex::new(()); +10 | | let _guard = mutex.lock().unwrap(); +11 | | f().await; +12 | | } + | |_____^ future returned by `__test` is not `Send` + | + = help: within `impl Future`, the trait `Send` is not implemented for `MutexGuard<'_, ()>` +note: future is not `Send` as this value is used across an await + --> $DIR/send-not-implemented.rs:11:9 + | +10 | let _guard = mutex.lock().unwrap(); + | ------ has type `MutexGuard<'_, ()>` which is not `Send` +11 | f().await; + | ^^^^^^^^^ await occurs here, with `_guard` maybe used later +12 | } + | - `_guard` is later dropped here + = note: required for the cast to the object type `dyn Future + Send` diff --git a/third_party/rust/async-trait/tests/ui/unsupported-self.rs b/third_party/rust/async-trait/tests/ui/unsupported-self.rs new file mode 100644 index 000000000000..5868c614ad8c --- /dev/null +++ b/third_party/rust/async-trait/tests/ui/unsupported-self.rs @@ -0,0 +1,15 @@ +use async_trait::async_trait; + +#[async_trait] +pub trait Trait { + async fn method(); +} + +#[async_trait] +impl Trait for &'static str { + async fn method() { + let _ = Self; + } +} + +fn main() {} diff --git a/third_party/rust/async-trait/tests/ui/unsupported-self.stderr b/third_party/rust/async-trait/tests/ui/unsupported-self.stderr new file mode 100644 index 000000000000..c1ea955a2a6d --- /dev/null +++ b/third_party/rust/async-trait/tests/ui/unsupported-self.stderr @@ -0,0 +1,5 @@ +error: Self type of this impl is unsupported in expression position + --> $DIR/unsupported-self.rs:11:17 + | +11 | let _ = Self; + | ^^^^ diff --git a/third_party/rust/chunky-vec/.cargo-checksum.json b/third_party/rust/chunky-vec/.cargo-checksum.json new file mode 100644 index 000000000000..8d85d17e6d68 --- /dev/null +++ b/third_party/rust/chunky-vec/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"3ab88cdacffa2756abe4460dda1ef403b304e79f814a2ec71b9c9a013dce2bf6","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"ea4a2543160e8e33d4e2d2b40339c90e0ab2960cfa59a65264b66782d7f9e35f","README.md":"382da770bccf4ebe07dded523a9ab5e4daead902edbbbffd20740404e73ee3d5","src/lib.rs":"01dc7e2a981fc95825584916d320fd60232b07fc88761126c89b12da3e3f20e5"},"package":"bb7bdea464ae038f09197b82430b921c53619fc8d2bcaf7b151013b3ca008017"} \ No newline at end of file diff --git a/third_party/rust/chunky-vec/Cargo.toml b/third_party/rust/chunky-vec/Cargo.toml new file mode 100644 index 000000000000..f436628b35f0 --- /dev/null +++ b/third_party/rust/chunky-vec/Cargo.toml @@ -0,0 +1,24 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies +# +# If you believe there's an error in this file please file an +# issue against the rust-lang/cargo repository. If you're +# editing this file be aware that the upstream Cargo.toml +# will likely look very different (and much more reasonable) + +[package] +edition = "2018" +name = "chunky-vec" +version = "0.1.0" +authors = ["Dan Glastonbury "] +description = "A pin safe, append only vector never moves the backing store for an element.\n" +keywords = ["data-structure", "vector", "pin"] +categories = ["data-structures"] +license = "Apache-2.0/MIT" +repository = "https://github.com/djg/chunky-vec" + +[dependencies] diff --git a/third_party/rust/chunky-vec/LICENSE-APACHE b/third_party/rust/chunky-vec/LICENSE-APACHE new file mode 100644 index 000000000000..16fe87b06e80 --- /dev/null +++ b/third_party/rust/chunky-vec/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/third_party/rust/chunky-vec/LICENSE-MIT b/third_party/rust/chunky-vec/LICENSE-MIT new file mode 100644 index 000000000000..f79cf5d3d2b7 --- /dev/null +++ b/third_party/rust/chunky-vec/LICENSE-MIT @@ -0,0 +1,27 @@ +MIT License + +Copyright (c) 2020 Daniel Glastonbury + +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/third_party/rust/chunky-vec/README.md b/third_party/rust/chunky-vec/README.md new file mode 100644 index 000000000000..e57317c01341 --- /dev/null +++ b/third_party/rust/chunky-vec/README.md @@ -0,0 +1,6 @@ +# Chunky Vec + +[![License: MIT/Apache-2.0](https://img.shields.io/crates/l/chunky-vec.svg)](#license) + +This crate provides a pin-safe, append-only vector which guarantees never +to move the storage for an element once it has been allocated. diff --git a/third_party/rust/chunky-vec/src/lib.rs b/third_party/rust/chunky-vec/src/lib.rs new file mode 100644 index 000000000000..dc815525539b --- /dev/null +++ b/third_party/rust/chunky-vec/src/lib.rs @@ -0,0 +1,343 @@ +//! This crate provides a pin-safe, append-only vector which guarantees never +//! to move the storage for an element once it has been allocated. + +use std::{ + ops::{Index, IndexMut}, + slice, +}; + +struct Chunk { + /// The elements of this chunk. + elements: Vec, +} + +impl Chunk { + fn with_capacity(capacity: usize) -> Self { + let elements = Vec::with_capacity(capacity); + assert_eq!(elements.capacity(), capacity); + Self { elements } + } + + fn len(&self) -> usize { + self.elements.len() + } + + /// Returns the number of available empty elements. + fn available(&self) -> usize { + self.elements.capacity() - self.elements.len() + } + + /// Returns a shared reference to the element at the given index. + /// + /// # Panics + /// + /// Panics if the index is out of bounds. + pub fn get(&self, index: usize) -> Option<&T> { + self.elements.get(index) + } + + /// Returns an exclusive reference to the element at the given index. + /// + /// # Panics + /// + /// Panics if the index is out of bounds. + pub fn get_mut(&mut self, index: usize) -> Option<&mut T> { + self.elements.get_mut(index) + } + + /// Pushes a new value into the fixed capacity entry. + /// + /// # Panics + /// + /// If the entry is already at its capacity. + /// Note that this panic should never happen since the entry is only ever + /// accessed by its outer chunk vector that checks before pushing. + pub fn push(&mut self, new_value: T) { + if self.available() == 0 { + panic!("No available elements.") + } + self.elements.push(new_value); + } + + pub fn push_get(&mut self, new_value: T) -> &mut T { + self.push(new_value); + unsafe { + let last = self.elements.len() - 1; + self.elements.get_unchecked_mut(last) + } + } + + /// Returns an iterator over the elements of the chunk. + pub fn iter(&self) -> slice::Iter { + self.elements.iter() + } + + /// Returns an iterator over the elements of the chunk. + pub fn iter_mut(&mut self) -> slice::IterMut { + self.elements.iter_mut() + } +} + +impl Index for Chunk { + type Output = T; + + fn index(&self, index: usize) -> &Self::Output { + self.get(index).expect("index out of bounds") + } +} + +impl IndexMut for Chunk { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + self.get_mut(index).expect("index out of bounds") + } +} + +/// Pin safe vector +/// +/// An append only vector that never moves the backing store for each element. +pub struct ChunkyVec { + /// The chunks holding elements + chunks: Vec>, +} + +impl Default for ChunkyVec { + fn default() -> Self { + Self { + chunks: Vec::default(), + } + } +} + +impl Unpin for ChunkyVec {} + +impl ChunkyVec { + const DEFAULT_CAPACITY: usize = 32; + + pub fn len(&self) -> usize { + if self.chunks.is_empty() { + 0 + } else { + // # Safety - There is at least one chunk here. + (self.chunks.len() - 1) * Self::DEFAULT_CAPACITY + self.chunks.last().unwrap().len() + } + } + + pub fn is_empty(&self) -> bool { + // # Safety - Since it's impossible to pop, at least one chunk means we're not empty. + self.chunks.is_empty() + } + + /// Returns an iterator that yields shared references to the elements of the bucket vector. + pub fn iter(&self) -> Iter { + Iter::new(self) + } + + /// Returns an iterator that yields exclusive reference to the elements of the bucket vector. + pub fn iter_mut(&mut self) -> IterMut { + IterMut::new(self) + } + + /// Returns a shared reference to the element at the given index if any. + pub fn get(&self, index: usize) -> Option<&T> { + let (x, y) = ( + index / Self::DEFAULT_CAPACITY, + index % Self::DEFAULT_CAPACITY, + ); + self.chunks.get(x).and_then(|chunk| chunk.get(y)) + } + + /// Returns an exclusive reference to the element at the given index if any. + pub fn get_mut(&mut self, index: usize) -> Option<&mut T> { + let (x, y) = ( + index / Self::DEFAULT_CAPACITY, + index % Self::DEFAULT_CAPACITY, + ); + self.chunks.get_mut(x).and_then(|chunk| chunk.get_mut(y)) + } + + /// Pushes a new element onto the bucket vector. + /// + /// # Note + /// + /// This operation will never move other elements, reallocates or otherwise + /// invalidate pointers of elements contained by the bucket vector. + pub fn push(&mut self, new_value: T) { + self.push_get(new_value); + } + + pub fn push_get(&mut self, new_value: T) -> &mut T { + if self.chunks.last().map(Chunk::available).unwrap_or_default() == 0 { + self.chunks.push(Chunk::with_capacity(Self::DEFAULT_CAPACITY)); + } + // Safety: Guaranteed to have a chunk with available elements + unsafe { + let last = self.chunks.len() - 1; + self.chunks.get_unchecked_mut(last).push_get(new_value) + } + } +} + +impl Index for ChunkyVec { + type Output = T; + + fn index(&self, index: usize) -> &Self::Output { + self.get(index).expect("index out of bounds") + } +} + +impl IndexMut for ChunkyVec { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + self.get_mut(index).expect("index out of bounds") + } +} + +/// An iterator yielding shared references to the elements of a ChunkyVec. +#[derive(Clone)] +pub struct Iter<'a, T> { + /// Chunks iterator. + chunks: slice::Iter<'a, Chunk>, + /// Forward iterator for `next`. + iter: Option>, + /// Number of elements that are to be yielded by the iterator. + len: usize, +} + +impl<'a, T> Iter<'a, T> { + /// Creates a new iterator over the ChunkyVec + pub(crate) fn new(vec: &'a ChunkyVec) -> Self { + let len = vec.len(); + Self { + chunks: vec.chunks.iter(), + iter: None, + len, + } + } +} + +impl<'a, T> Iterator for Iter<'a, T> { + type Item = &'a T; + + fn next(&mut self) -> Option { + loop { + if let Some(ref mut iter) = self.iter { + if let front @ Some(_) = iter.next() { + self.len -= 1; + return front; + } + } + self.iter = Some(self.chunks.next()?.iter()); + } + } + + fn size_hint(&self) -> (usize, Option) { + (self.len(), Some(self.len())) + } +} + +impl<'a, T> ExactSizeIterator for Iter<'a, T> { + fn len(&self) -> usize { + self.len + } +} + +/// An iterator yielding exclusive references to the elements of a ChunkVec. +pub struct IterMut<'a, T> { + /// Chunks iterator. + chunks: slice::IterMut<'a, Chunk>, + /// Forward iterator for `next`. + iter: Option>, + /// Number of elements that are to be yielded by the iterator. + len: usize, +} + +impl<'a, T> IterMut<'a, T> { + /// Creates a new iterator over the bucket vector. + pub(crate) fn new(vec: &'a mut ChunkyVec) -> Self { + let len = vec.len(); + Self { + chunks: vec.chunks.iter_mut(), + iter: None, + len, + } + } +} + +impl<'a, T> Iterator for IterMut<'a, T> { + type Item = &'a mut T; + + fn next(&mut self) -> Option { + loop { + if let Some(ref mut iter) = self.iter { + if let front @ Some(_) = iter.next() { + self.len -= 1; + return front; + } + } + self.iter = Some(self.chunks.next()?.iter_mut()); + } + } + + fn size_hint(&self) -> (usize, Option) { + (self.len(), Some(self.len())) + } +} + +impl<'a, T> ExactSizeIterator for IterMut<'a, T> { + fn len(&self) -> usize { + self.len + } +} +impl<'a, T> IntoIterator for &'a ChunkyVec { + type Item = &'a T; + type IntoIter = Iter<'a, T>; + + fn into_iter(self) -> Iter<'a, T> { + self.iter() + } +} + +impl<'a, T> IntoIterator for &'a mut ChunkyVec { + type Item = &'a mut T; + type IntoIter = IterMut<'a, T>; + + fn into_iter(self) -> IterMut<'a, T> { + self.iter_mut() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn iterate_empty() { + let v = ChunkyVec::::default(); + for i in &v { + println!("{:?}", i); + } + } + + #[test] + fn iterate_multiple_chunks() { + let mut v = ChunkyVec::::default(); + for i in 0..33 { + v.push(i); + } + let mut iter = v.iter(); + for _ in 0..32 { + iter.next(); + } + assert_eq!(iter.next(), Some(&32)); + assert_eq!(iter.next(), None); + } + + #[test] + fn index_multiple_chunks() { + let mut v = ChunkyVec::::default(); + for i in 0..33 { + v.push(i); + } + assert_eq!(v.get(32), Some(&32)); + assert_eq!(v[32], 32); + } +} diff --git a/third_party/rust/fluent-bundle/.cargo-checksum.json b/third_party/rust/fluent-bundle/.cargo-checksum.json index 5febe99068eb..28126db47184 100644 --- a/third_party/rust/fluent-bundle/.cargo-checksum.json +++ b/third_party/rust/fluent-bundle/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"a12d7a4455cac14350f4b9823c67d23d6813ea6a10e8642f1b9f01711bf45b70","LICENSE-APACHE":"5db2b182453ff32ed40f7da63589c9667a3f8bd8b16b1471b152caae56f77e45","LICENSE-MIT":"49c0b000c03731d9e3970dc059ad4ca345d773681f4a612b0024435b663e0220","README.md":"d74aad87a35e7d0a87ad5a2fc523f437ea6ceb419290cdb55f55257bcbfc0464","benches/resolver.rs":"bd46c8b710ac1898a0e69324a7ecf9aa38d577337bc5855a07ca0ad1043603a1","benches/resolver_iai.rs":"e9e940f4c09908069d474d379a0230dfc6fa44325300d72975e8f1d9ef64f6f1","examples/README.md":"99a51f7d388d2da3c5291e68de5264feaf642ba9a22f6f882c3b940c1b6429b2","src/args.rs":"51346e9ec84f2eeb4462e0e993b1bbb307585a2a40e41f6d0d745889bca56a7d","src/bundle.rs":"3da63b685acf559ee80fa489885da126f7c68405026ac065a07e559e2186a77f","src/concurrent.rs":"be77275513918809b98c554b26a65c6a9cf2a7bf52db3bbaf21ebdd34d94c651","src/entry.rs":"e1507b0e4c3e6d0d2efc5d622f4156a5156b9eeb40d9c5353cb7fdc236c38189","src/errors.rs":"a357f3a09335d31e362aa99a8d82eab4e238fdec8498141990f61ede58f4dabb","src/lib.rs":"42ac33b188b28159b3c683b8af8f739f063b3a372eea22b3022a899b0360e69a","src/memoizer.rs":"922084f71f02d0532056db9b41cec4c1434001fe60215ee6f6ac8e3fd2518f12","src/message.rs":"4a3c95d3ecd016aeaa5da07e99d78de62f13aac8aa447818aabd0f63f2d143c4","src/resolver/errors.rs":"beaf41fabbfd11211cb2c3db6ca0ba26bccf75817bed05a92b980393edfb3f9f","src/resolver/expression.rs":"f18413de1a6b3ba43c062e24d58a60d63f4dad66bcec67ed55756ff5014f9347","src/resolver/inline_expression.rs":"089ad6745d0790478ea698fd530f2236c550889f9be75e245ed94bba4b883884","src/resolver/mod.rs":"b367ed2a9bbb835c145387f13bec62eb9807c8ff41d73d6a945edec0cc760f3d","src/resolver/pattern.rs":"85542a4f161b161ef85da539c8710b9a9082b6c5279f3cc3867786ad31de6d35","src/resolver/scope.rs":"816f51146c38affea54c6e0911e3522f998485829e619cca5f72cec05180de59","src/resource.rs":"40f81ae728c427343f972bfc1ce96924ac977fa7082904d56cbad0c482a09ce1","src/types/mod.rs":"1cd65301fb32233fd241a79c6fa873edcb40a271330601360f72e2c452900509","src/types/number.rs":"2d3b403e5f545e2f4a3a16aec0bb019a4cc8e5ac0ab2db5642ba8039fbe203a8","src/types/plural.rs":"f28834e71d6970d5eb48089132f5242433b1e62b90765d85e3c76f805eecc92e"},"package":"5b589dfaa7e69ddf497be48cd0d184d7ff6e2cbb8186d1bb01c26d5cf5449a17"} \ No newline at end of file +{"files":{"Cargo.toml":"98dcbe4ffe6955c65496f4a528d4aa34d6672c7d920ee393f89a32aababaae9a","LICENSE-APACHE":"5db2b182453ff32ed40f7da63589c9667a3f8bd8b16b1471b152caae56f77e45","LICENSE-MIT":"49c0b000c03731d9e3970dc059ad4ca345d773681f4a612b0024435b663e0220","README.md":"d74aad87a35e7d0a87ad5a2fc523f437ea6ceb419290cdb55f55257bcbfc0464","benches/resolver.rs":"bd46c8b710ac1898a0e69324a7ecf9aa38d577337bc5855a07ca0ad1043603a1","benches/resolver_iai.rs":"e9e940f4c09908069d474d379a0230dfc6fa44325300d72975e8f1d9ef64f6f1","examples/README.md":"99a51f7d388d2da3c5291e68de5264feaf642ba9a22f6f882c3b940c1b6429b2","src/args.rs":"51346e9ec84f2eeb4462e0e993b1bbb307585a2a40e41f6d0d745889bca56a7d","src/bundle.rs":"3da63b685acf559ee80fa489885da126f7c68405026ac065a07e559e2186a77f","src/concurrent.rs":"be77275513918809b98c554b26a65c6a9cf2a7bf52db3bbaf21ebdd34d94c651","src/entry.rs":"e1507b0e4c3e6d0d2efc5d622f4156a5156b9eeb40d9c5353cb7fdc236c38189","src/errors.rs":"a357f3a09335d31e362aa99a8d82eab4e238fdec8498141990f61ede58f4dabb","src/lib.rs":"58a0c929322f83aac41280da035b50adbcdb05d8a8376359d58c177cd9755eab","src/memoizer.rs":"922084f71f02d0532056db9b41cec4c1434001fe60215ee6f6ac8e3fd2518f12","src/message.rs":"4a3c95d3ecd016aeaa5da07e99d78de62f13aac8aa447818aabd0f63f2d143c4","src/resolver/errors.rs":"beaf41fabbfd11211cb2c3db6ca0ba26bccf75817bed05a92b980393edfb3f9f","src/resolver/expression.rs":"f18413de1a6b3ba43c062e24d58a60d63f4dad66bcec67ed55756ff5014f9347","src/resolver/inline_expression.rs":"089ad6745d0790478ea698fd530f2236c550889f9be75e245ed94bba4b883884","src/resolver/mod.rs":"d1b15ce110ea49876909412c12c4c1841052bb80f4838a934dfccd6a5264855c","src/resolver/pattern.rs":"64162a7e2ad0df82d463d14ac6a472005bba4cef4a7e73fe2a9529e811124a85","src/resolver/scope.rs":"816f51146c38affea54c6e0911e3522f998485829e619cca5f72cec05180de59","src/resource.rs":"40f81ae728c427343f972bfc1ce96924ac977fa7082904d56cbad0c482a09ce1","src/types/mod.rs":"1cd65301fb32233fd241a79c6fa873edcb40a271330601360f72e2c452900509","src/types/number.rs":"2d3b403e5f545e2f4a3a16aec0bb019a4cc8e5ac0ab2db5642ba8039fbe203a8","src/types/plural.rs":"f28834e71d6970d5eb48089132f5242433b1e62b90765d85e3c76f805eecc92e"},"package":"8acf044eeb4872d9dbf2667541fbf461f5965c57e343878ad0fb24b5793fa007"} \ No newline at end of file diff --git a/third_party/rust/fluent-bundle/Cargo.toml b/third_party/rust/fluent-bundle/Cargo.toml index 2e12474000a8..967247d5a1d6 100644 --- a/third_party/rust/fluent-bundle/Cargo.toml +++ b/third_party/rust/fluent-bundle/Cargo.toml @@ -13,7 +13,7 @@ [package] edition = "2018" name = "fluent-bundle" -version = "0.15.0" +version = "0.15.1" authors = ["Zibi Braniecki ", "Staś Małolepszy "] include = ["src/**/*", "benches/*.rs", "Cargo.toml", "README.md", "LICENSE-APACHE", "LICENSE-MIT"] description = "A localization system designed to unleash the entire expressive power of\nnatural language translations.\n" @@ -44,7 +44,7 @@ version = "0.5" version = "7.0.1" [dependencies.ouroboros] -version = "0.8" +version = "0.9" [dependencies.rustc-hash] version = "1" diff --git a/third_party/rust/fluent-bundle/src/lib.rs b/third_party/rust/fluent-bundle/src/lib.rs index b985a9c3d5e3..faf3e9ba60fd 100644 --- a/third_party/rust/fluent-bundle/src/lib.rs +++ b/third_party/rust/fluent-bundle/src/lib.rs @@ -1,6 +1,6 @@ //! Fluent is a modern localization system designed to improve how software is translated. //! -//! `fluent-bundle` is the mid-level component of the [Fluent Localization +//! `fluent-bundle` is a mid-level component of the [Fluent Localization //! System](https://www.projectfluent.org). //! //! The crate builds on top of the low level [`fluent-syntax`](../fluent-syntax) package, and provides diff --git a/third_party/rust/fluent-bundle/src/resolver/mod.rs b/third_party/rust/fluent-bundle/src/resolver/mod.rs index 901148444d92..f137bcc91b89 100644 --- a/third_party/rust/fluent-bundle/src/resolver/mod.rs +++ b/third_party/rust/fluent-bundle/src/resolver/mod.rs @@ -1,4 +1,4 @@ -mod errors; +pub mod errors; mod expression; mod inline_expression; mod pattern; diff --git a/third_party/rust/fluent-bundle/src/resolver/pattern.rs b/third_party/rust/fluent-bundle/src/resolver/pattern.rs index 7768d1514322..4e01d4ca47be 100644 --- a/third_party/rust/fluent-bundle/src/resolver/pattern.rs +++ b/third_party/rust/fluent-bundle/src/resolver/pattern.rs @@ -49,15 +49,16 @@ impl<'p> WriteValue for ast::Pattern<&'p str> { let needs_isolation = scope.bundle.use_isolating && len > 1 - && !matches!(expression, ast::Expression::Inline( - ast::InlineExpression::MessageReference { .. }, - ) - | ast::Expression::Inline( - ast::InlineExpression::TermReference { .. }, - ) - | ast::Expression::Inline( - ast::InlineExpression::StringLiteral { .. }, - )); + && !matches!( + expression, + ast::Expression::Inline(ast::InlineExpression::MessageReference { .. },) + | ast::Expression::Inline( + ast::InlineExpression::TermReference { .. }, + ) + | ast::Expression::Inline( + ast::InlineExpression::StringLiteral { .. }, + ) + ); if needs_isolation { w.write_char('\u{2068}')?; } diff --git a/third_party/rust/fluent-fallback/.cargo-checksum.json b/third_party/rust/fluent-fallback/.cargo-checksum.json new file mode 100644 index 000000000000..57042305381b --- /dev/null +++ b/third_party/rust/fluent-fallback/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"CHANGELOG.md":"a878b5f0c0102bfeba0991554ff71b869ac8eb39c2fafe96c3fdeb6b903b9f9b","Cargo.lock":"7517e395531cd8bc810ffbe1a97d45d78d65e25611fef6a9314cfc279da0a82d","Cargo.toml":"a9da4fd94ea03886127fdd5fffdeaf7ae5107ac287a9d5eeaafc38e2a374baeb","LICENSE-APACHE":"5db2b182453ff32ed40f7da63589c9667a3f8bd8b16b1471b152caae56f77e45","LICENSE-MIT":"49c0b000c03731d9e3970dc059ad4ca345d773681f4a612b0024435b663e0220","README.md":"0e3c37b7cd14a1c672125f58a6dbc5eb43d11c3c67f2ada7f77569e788b9f435","examples/resources/en-US/simple.ftl":"55e8a72973d239c6ed3eb3d9cbc21d37dc90cea9fad85d1d8d73c96d63941629","examples/resources/pl/simple.ftl":"d63d7c62c225897d9f28f166c17e038b8f780dca9e9ee640e81360f23219a212","examples/simple-fallback.rs":"c5033e15c9d0cd9270c57fd498509e17202c8a5c4332e74170184cb2b3e05f85","src/bundles.rs":"f452624b00e7ee01be9b91d702ee28e8900d309597a3340baa8426a72f2697e7","src/cache.rs":"d0e886b95999120baf513d0438491c68cf37e9f99c515cdcf250ffc8f263f439","src/env.rs":"c11b27dcaf76a0f69d31007f8027cbdc6fb1330082afd28cf9f30aeb0352d127","src/errors.rs":"bf1977011093eace65a32fea782776431a3ce718b2454c7c98f5c7364298c240","src/generator.rs":"cf8272de5f97a1efd4a85504363196ebf79826c01dc86056b165d473b7b2b90d","src/lib.rs":"73c1793d76de949a1980424912525ad7f5d4183310d8ce5db3ec8efef2eb2b33","src/localization.rs":"a76bb9c3f89365c88fc6bf18e4f2966d15148b70584d7f5988c76b96018c07fc","src/pin_cell/README.md":"b230e479f0ce5de00ce6638aa47cdf1bd30a934df5f3ad33523c3b9f16ffb02b","src/pin_cell/mod.rs":"7247334eb4c6753babe8aeceacf1b36bdbbd60aed86754da61e09e87f1da24d1","src/pin_cell/pin_mut.rs":"116d0ac2353fbc4d2d1084610f90a9aa414b4607bb01595528025ed49889127c","src/pin_cell/pin_ref.rs":"e67ef14faf7d1d47e082732d3a5446e4ae78a25b9577f0819faa1705b236f01d","src/types.rs":"c4fba75e632b7ec80a40559437e284b61874b6ea29393d3634b9f6eef5f72adb","tests/localization_test.rs":"ba476dd1a7f435e3350a8661a0283ba328912f4efe30288b62afa10cbdca59d8","tests/resources/en-US/test.ftl":"1103dafb98582e728ddcd30b3fa8ffc1b9d4231cc86296f4e2d8865e9d40b25d","tests/resources/en-US/test2.ftl":"821c99ed74f57635e02877fccf6c2ac9e388997e646c850cd0f86cbd3238b490","tests/resources/pl/test.ftl":"5f7f7a9a8ef2c7175c2a9e2d68ff3748667e8ede877bb6b8a72337ac24e5dfeb","tests/resources/pl/test2.ftl":"68550e8e37adfb49c03a95e6b0a6501d58fbfb6e498cda00b52fc258758245b9"},"package":"768feaf8a77beababd5cf3bb1154597b7161eb5a555013a3ac594fe3ed8b18ee"} \ No newline at end of file diff --git a/third_party/rust/fluent-fallback/CHANGELOG.md b/third_party/rust/fluent-fallback/CHANGELOG.md new file mode 100644 index 000000000000..98928d1d0ad7 --- /dev/null +++ b/third_party/rust/fluent-fallback/CHANGELOG.md @@ -0,0 +1,61 @@ +# Changelog + +## Unreleased + + - … + +## fluent-fallback 0.5.0 (Jul 8, 2021) + - Separate out `Bundles` for iterator state management. + +## fluent-fallback 0.4.4 (May 3, 2021) + - Fix waiting from multiple tasks. (#224) + - Bind locale iterator generics of `LocalesProvider` and `BundleGenerator`. + +## fluent-fallback 0.4.3 (April 26, 2021) + - Align errors even closer to fluent.js + +## fluent-fallback 0.4.2 (April 9, 2021) + - Align errors closer to fluent.js + +## fluent-fallback 0.4.0 (February 9, 2021) + - Use `fluent-bundle` 0.15. + +## fluent-fallback 0.3.0 (February 3, 2021) + - Handle locale management in `Localization`. + +## fluent-fallback 0.2.2 (January 16, 2021) + - Invalidate bundles on resource list change. + +## fluent-fallback 0.2.1 (January 15, 2021) + - Add `Localization::is_sync` + +## fluent-fallback 0.2.0 (January 12, 2021) + - Separate `Sync` and `Async` bundle generators. + - Reorganize fallback logic. + - Separate out prefetching trait. + - Vendor in pin-cell. + +## fluent-fallback 0.1.0 (January 3, 2021) + - Update `fluent-bundle` to 0.14. + - Switch from `elsa` to `chunky-vec`. + - Add `Localization::with_generator`. + - Add support for Streamed bundles. + - Add `LocalizationError`. + - Make `L10nKey`, `L10nMessage` and `L10nAttribute` types. + +## fluent-fallback 0.0.4 (May 6, 2020) + - Update `fluent-bundle` to 0.12. + - Update `unic-langid` to 0.9. + +## fluent-fallback 0.0.3 (February 13, 2020) + - Update `fluent-bundle` to 0.10. + - Update `unic-langid` to 0.8. + +## fluent-fallback 0.0.2 (November 26, 2019) + - Update `fluent-bundle` to 0.9. + - Update `unic-langid` to 0.7. + +## fluent-fallback 0.0.1 (August 1, 2019) + + - This is the first release to be listed in the CHANGELOG. + - Basic support for language fallbacking and runtime locale changes. diff --git a/third_party/rust/fluent-fallback/Cargo.lock b/third_party/rust/fluent-fallback/Cargo.lock new file mode 100644 index 000000000000..5a2a643e1472 --- /dev/null +++ b/third_party/rust/fluent-fallback/Cargo.lock @@ -0,0 +1,468 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" + +[[package]] +name = "async-trait" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b98e84bbb4cbcdd97da190ba0c58a1bb0de2c1fdf67d159e192ed766aeca722" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "chunky-vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb7bdea464ae038f09197b82430b921c53619fc8d2bcaf7b151013b3ca008017" + +[[package]] +name = "fluent-bundle" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acf044eeb4872d9dbf2667541fbf461f5965c57e343878ad0fb24b5793fa007" +dependencies = [ + "fluent-langneg", + "fluent-syntax", + "intl-memoizer", + "intl_pluralrules", + "ouroboros", + "rustc-hash", + "smallvec", + "unic-langid", +] + +[[package]] +name = "fluent-fallback" +version = "0.5.0" +dependencies = [ + "async-trait", + "chunky-vec", + "fluent-bundle", + "fluent-langneg", + "futures", + "once_cell", + "tokio", + "unic-langid", +] + +[[package]] +name = "fluent-langneg" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ad0989667548f06ccd0e306ed56b61bd4d35458d54df5ec7587c0e8ed5e94" +dependencies = [ + "unic-langid", +] + +[[package]] +name = "fluent-syntax" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0abed97648395c902868fee9026de96483933faa54ea3b40d652f7dfe61ca78" +dependencies = [ + "thiserror", +] + +[[package]] +name = "futures" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7e43a803dae2fa37c1f6a8fe121e1f7bf9548b4dfc0522a42f34145dadfc27" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e682a68b29a882df0545c143dc3646daefe80ba479bcdede94d5a703de2871e2" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0402f765d8a89a26043b889b26ce3c4679d268fa6bb22cd7c6aad98340e179d1" + +[[package]] +name = "futures-executor" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "badaa6a909fac9e7236d0620a2f57f7664640c56575b71a7552fbd68deafab79" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acc499defb3b348f8d8f3f66415835a9131856ff7714bf10dadfc4ec4bdb29a1" + +[[package]] +name = "futures-macro" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c40298486cdf52cc00cd6d6987892ba502c7656a16a4192a9992b1ccedd121" +dependencies = [ + "autocfg", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a57bead0ceff0d6dde8f465ecd96c9338121bb7717d3e7b108059531870c4282" + +[[package]] +name = "futures-task" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a16bef9fc1a4dddb5bee51c989e3fbba26569cbb0e31f5b303c184e3dd33dae" + +[[package]] +name = "futures-util" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "feb5c238d27e2bf94ffdfd27b2c29e3df4a68c4193bb6427384259e2bf191967" +dependencies = [ + "autocfg", + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "proc-macro-hack", + "proc-macro-nested", + "slab", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "intl-memoizer" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c310433e4a310918d6ed9243542a6b83ec1183df95dff8f23f87bb88a264a66f" +dependencies = [ + "type-map", + "unic-langid", +] + +[[package]] +name = "intl_pluralrules" +version = "7.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b18f988384267d7066cc2be425e6faf352900652c046b6971d2e228d3b1c5ecf" +dependencies = [ + "tinystr", + "unic-langid", +] + +[[package]] +name = "libc" +version = "0.2.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790" + +[[package]] +name = "memchr" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" + +[[package]] +name = "ouroboros" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbeff60e3e37407a80ead3e9458145b456e978c4068cddbfea6afb48572962ca" +dependencies = [ + "ouroboros_macro", + "stable_deref_trait", +] + +[[package]] +name = "ouroboros_macro" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03f2cb802b5bdfdf52f1ffa0b54ce105e4d346e91990dd571f86c91321ad49e2" +dependencies = [ + "Inflector", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + +[[package]] +name = "proc-macro-nested" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" + +[[package]] +name = "proc-macro2" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "slab" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527" + +[[package]] +name = "smallvec" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "syn" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "thiserror" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinystr" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29738eedb4388d9ea620eeab9384884fc3f06f586a2eddb56bedc5885126c7c1" + +[[package]] +name = "tokio" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98c8b05dc14c75ea83d63dd391100353789f5f24b8b3866542a5e85c8be8e985" +dependencies = [ + "autocfg", + "num_cpus", + "pin-project-lite", + "tokio-macros", +] + +[[package]] +name = "tokio-macros" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54473be61f4ebe4efd09cec9bd5d16fa51d70ea0192213d754d2d500457db110" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "type-map" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d3364c5e96cb2ad1603037ab253ddd34d7fb72a58bdddf4b7350760fc69a46" +dependencies = [ + "rustc-hash", +] + +[[package]] +name = "unic-langid" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73328fcd730a030bdb19ddf23e192187a6b01cd98be6d3140622a89129459ce5" +dependencies = [ + "unic-langid-impl", + "unic-langid-macros", +] + +[[package]] +name = "unic-langid-impl" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a4a8eeaf0494862c1404c95ec2f4c33a2acff5076f64314b465e3ddae1b934d" +dependencies = [ + "tinystr", +] + +[[package]] +name = "unic-langid-macros" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18f980d6d87e8805f2836d64b4138cc95aa7986fa63b1f51f67d5fbff64dd6e5" +dependencies = [ + "proc-macro-hack", + "tinystr", + "unic-langid-impl", + "unic-langid-macros-impl", +] + +[[package]] +name = "unic-langid-macros-impl" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29396ffd97e27574c3e01368b1a64267d3064969e4848e2e130ff668be9daa9f" +dependencies = [ + "proc-macro-hack", + "quote", + "syn", + "unic-langid-impl", +] + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" diff --git a/third_party/rust/fluent-fallback/Cargo.toml b/third_party/rust/fluent-fallback/Cargo.toml new file mode 100644 index 000000000000..a125d845b651 --- /dev/null +++ b/third_party/rust/fluent-fallback/Cargo.toml @@ -0,0 +1,51 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies +# +# If you believe there's an error in this file please file an +# issue against the rust-lang/cargo repository. If you're +# editing this file be aware that the upstream Cargo.toml +# will likely look very different (and much more reasonable) + +[package] +edition = "2018" +name = "fluent-fallback" +version = "0.5.0" +authors = ["Zibi Braniecki ", "Staś Małolepszy "] +description = "High-level abstraction model for managing localization resources\nand runtime localization lifecycle.\n" +homepage = "http://www.projectfluent.org" +readme = "README.md" +keywords = ["localization", "l10n", "i18n", "intl", "internationalization"] +categories = ["localization", "internationalization"] +license = "Apache-2.0/MIT" +repository = "https://github.com/projectfluent/fluent-rs" +[dependencies.async-trait] +version = "0.1" + +[dependencies.chunky-vec] +version = "0.1" + +[dependencies.fluent-bundle] +version = "0.15.1" + +[dependencies.futures] +version = "0.3" + +[dependencies.once_cell] +version = "1.8" + +[dependencies.unic-langid] +version = "0.9" +[dev-dependencies.fluent-langneg] +version = "0.13" + +[dev-dependencies.tokio] +version = "1.0" +features = ["rt-multi-thread", "macros"] + +[dev-dependencies.unic-langid] +version = "0.9" +features = ["macros"] diff --git a/third_party/rust/fluent-fallback/LICENSE-APACHE b/third_party/rust/fluent-fallback/LICENSE-APACHE new file mode 100644 index 000000000000..35582f166b4b --- /dev/null +++ b/third_party/rust/fluent-fallback/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2017 Mozilla + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/third_party/rust/fluent-fallback/LICENSE-MIT b/third_party/rust/fluent-fallback/LICENSE-MIT new file mode 100644 index 000000000000..5655fa311cd2 --- /dev/null +++ b/third_party/rust/fluent-fallback/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright 2017 Mozilla + +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/third_party/rust/fluent-fallback/README.md b/third_party/rust/fluent-fallback/README.md new file mode 100644 index 000000000000..11986da5b0b9 --- /dev/null +++ b/third_party/rust/fluent-fallback/README.md @@ -0,0 +1,102 @@ +# Fluent + +`fluent-fallback` is a Rust implementation of the [Project Fluent][] higher level API. + +The `Localization` struct encapsulates a persistant localization context providing +language fallbacking. The instance remains available throughout the whole life cycle of +the corresponding UI, reacting to events such as locale changes, resource updates etc. + +The API can be used directly, or can serve as an example of state manager for `fluent-bundle` and `fluent-resmgr`. + +[![crates.io](https://meritbadge.herokuapp.com/fluent-fallback)](https://crates.io/crates/fluent-fallback) +[![Build and test](https://github.com/projectfluent/fluent-rs/workflows/Build%20and%20test/badge.svg)](https://github.com/projectfluent/fluent-rs/actions?query=branch%3Amaster+workflow%3A%22Build+and+test%22) +[![Coverage Status](https://coveralls.io/repos/github/projectfluent/fluent-rs/badge.svg?branch=master)](https://coveralls.io/github/projectfluent/fluent-rs?branch=master) + +Project Fluent keeps simple things simple and makes complex things possible. +The syntax used for describing translations is easy to read and understand. At +the same time it allows, when necessary, to represent complex concepts from +natural languages like gender, plurals, conjugations, and others. + +[Documentation][] + +[Project Fluent]: http://projectfluent.org +[Documentation]: https://docs.rs/fluent/ + +Usage +----- + +```rust +use fluent_fallback::Localization; + +fn main() { + // generate_messages is a closure that returns an iterator over FluentBundle + // instances. + let loc = Localization::new(vec!["simple.ftl".into()], generate_messages); + + let value = bundle.format_value("hello-world", None); + + assert_eq!(&value, "Hello, world!"); +} +``` + + +Status +------ + +The implementation is in its early stages and supports only some of the Project +Fluent's spec. Consult the [list of milestones][] for more information about +release planning and scope. + +[list of milestones]: https://github.com/projectfluent/fluent-rs/milestones + + +Local Development +----------------- + + cargo build + cargo test + cargo run --example simple-fallback + +When submitting a PR please use [`cargo fmt`][] (nightly). + +[`cargo fmt`]: https://github.com/rust-lang-nursery/rustfmt + + +Learn the FTL syntax +-------------------- + +FTL is a localization file format used for describing translation resources. +FTL stands for _Fluent Translation List_. + +FTL is designed to be simple to read, but at the same time allows to represent +complex concepts from natural languages like gender, plurals, conjugations, and +others. + + hello-user = Hello, { $username }! + +[Read the Fluent Syntax Guide][] in order to learn more about the syntax. If +you're a tool author you may be interested in the formal [EBNF grammar][]. + +[Read the Fluent Syntax Guide]: http://projectfluent.org/fluent/guide/ +[EBNF grammar]: https://github.com/projectfluent/fluent/tree/master/spec + + +Get Involved +------------ + +`fluent-rs` is open-source, licensed under the Apache License, Version 2.0. We +encourage everyone to take a look at our code and we'll listen to your +feedback. + + +Discuss +------- + +We'd love to hear your thoughts on Project Fluent! Whether you're a localizer +looking for a better way to express yourself in your language, or a developer +trying to make your app localizable and multilingual, or a hacker looking for +a project to contribute to, please do get in touch on the mailing list and the +IRC channel. + + - Discourse: https://discourse.mozilla.org/c/fluent + - IRC channel: [irc://irc.mozilla.org/l20n](irc://irc.mozilla.org/l20n) diff --git a/third_party/rust/fluent-fallback/examples/resources/en-US/simple.ftl b/third_party/rust/fluent-fallback/examples/resources/en-US/simple.ftl new file mode 100644 index 000000000000..99f0a6bb6f18 --- /dev/null +++ b/third_party/rust/fluent-fallback/examples/resources/en-US/simple.ftl @@ -0,0 +1,7 @@ +missing-arg-error = Error: Please provide a number as argument. +input-parse-error = Error: Could not parse input `{ $input }`. Reason: { $reason } +response-msg = + { $value -> + [one] "{ $input }" has one Collatz step. + *[other] "{ $input }" has { $value } Collatz steps. + } diff --git a/third_party/rust/fluent-fallback/examples/resources/pl/simple.ftl b/third_party/rust/fluent-fallback/examples/resources/pl/simple.ftl new file mode 100644 index 000000000000..16173dd92e1d --- /dev/null +++ b/third_party/rust/fluent-fallback/examples/resources/pl/simple.ftl @@ -0,0 +1,8 @@ +missing-arg-error = Błąd: Proszę wprowadzić liczbę jako argument. +input-parse-error = Błąd: Nie udało się sparsować `{ $input }`. Powód: { $reason } +response-msg = + { $value -> + [one] "{ $input }" ma jeden krok Collatza. + [few] "{ $input }" ma { $value } kroki Collatza. + *[many] "{ $input }" ma { $value } kroków Collatza. + } diff --git a/third_party/rust/fluent-fallback/examples/simple-fallback.rs b/third_party/rust/fluent-fallback/examples/simple-fallback.rs new file mode 100644 index 000000000000..ad854c787b0f --- /dev/null +++ b/third_party/rust/fluent-fallback/examples/simple-fallback.rs @@ -0,0 +1,231 @@ +//! This is an example of a simple application +//! which calculates the Collatz conjecture. +//! +//! The function itself is trivial on purpose, +//! so that we can focus on understanding how +//! the application can be made localizable +//! via Fluent. +//! +//! To try the app launch `cargo run --example simple-fallback NUM (LOCALES)` +//! +//! NUM is a number to be calculated, and LOCALES is an optional +//! parameter with a comma-separated list of locales requested by the user. +//! +//! Example: +//! +//! cargo run --example simple-fallback 123 de,pl +//! +//! If the second argument is omitted, `en-US` locale is used as the +//! default one. + +use std::{env, fs, io, path::PathBuf, str::FromStr}; + +use fluent_bundle::{FluentArgs, FluentBundle, FluentResource}; +use fluent_fallback::{ + generator::{BundleGenerator, FluentBundleResult}, + Localization, +}; +use fluent_langneg::{negotiate_languages, NegotiationStrategy}; + +use unic_langid::{langid, LanguageIdentifier}; + +/// This helper struct holds the scheme for converting +/// resource paths into full paths. It is used to customise +/// `fluent-fallback::SyncLocalization`. +struct Bundles { + res_path_scheme: PathBuf, +} + +/// This helper function allows us to read the list +/// of available locales by reading the list of +/// directories in `./examples/resources`. +/// +/// It is expected that every directory inside it +/// has a name that is a valid BCP47 language tag. +fn get_available_locales() -> io::Result> { + let mut dir = env::current_dir()?; + if dir.to_string_lossy().ends_with("fluent-rs") { + dir.push("fluent-fallback"); + } + dir.push("examples"); + dir.push("resources"); + let res_dir = fs::read_dir(dir)?; + + let locales = res_dir + .into_iter() + .filter_map(|entry| entry.ok()) + .filter(|entry| entry.path().is_dir()) + .filter_map(|dir| { + let file_name = dir.file_name(); + let name = file_name.to_str()?; + Some(name.parse().expect("Parsing failed.")) + }) + .collect(); + Ok(locales) +} + +fn resolve_app_locales<'l>(args: &[String]) -> Vec { + let default_locale = langid!("en-US"); + let available = get_available_locales().expect("Retrieving available locales failed."); + + let requested: Vec = args.get(2).map_or(vec![], |arg| { + arg.split(",") + .map(|s| s.parse().expect("Parsing locale failed.")) + .collect() + }); + + negotiate_languages( + &requested, + &available, + Some(&default_locale), + NegotiationStrategy::Filtering, + ) + .into_iter() + .cloned() + .collect() +} + +fn get_resource_manager() -> Bundles { + let mut res_path_scheme = env::current_dir().expect("Failed to retrieve current dir."); + + if res_path_scheme.to_string_lossy().ends_with("fluent-rs") { + res_path_scheme.push("fluent-fallback"); + } + res_path_scheme.push("examples"); + res_path_scheme.push("resources"); + + res_path_scheme.push("{locale}"); + res_path_scheme.push("{res_id}"); + + Bundles { res_path_scheme } +} + +static L10N_RESOURCES: &[&str] = &["simple.ftl"]; + +fn main() { + let args: Vec = env::args().collect(); + + let app_locales: Vec = resolve_app_locales(&args); + + let bundles = get_resource_manager(); + + let loc = Localization::with_env( + L10N_RESOURCES.iter().map(|&res| res.into()).collect(), + true, + app_locales, + bundles, + ); + let bundles = loc.bundles(); + + let mut errors = vec![]; + + match args.get(1) { + Some(input) => match isize::from_str(&input) { + Ok(i) => { + let mut args = FluentArgs::new(); + args.set("input", i); + args.set("value", collatz(i)); + let value = bundles + .format_value_sync("response-msg", Some(&args), &mut errors) + .unwrap() + .unwrap(); + println!("{}", value); + } + Err(err) => { + let mut args = FluentArgs::new(); + args.set("input", input.as_str()); + args.set("reason", err.to_string()); + let value = bundles + .format_value_sync("input-parse-error-msg", Some(&args), &mut errors) + .unwrap() + .unwrap(); + println!("{}", value); + } + }, + None => { + let value = bundles + .format_value_sync("missing-arg-error", None, &mut errors) + .unwrap() + .unwrap(); + println!("{}", value); + } + } +} + +/// Collatz conjecture calculating function. +fn collatz(n: isize) -> isize { + match n { + 1 => 0, + _ => match n % 2 { + 0 => 1 + collatz(n / 2), + _ => 1 + collatz(n * 3 + 1), + }, + } +} + +/// Bundle iterator used by BundleGeneratorSync implementation for Locales. +struct BundleIter { + res_path_scheme: String, + locales: as IntoIterator>::IntoIter, + res_ids: Vec, +} + +impl Iterator for BundleIter { + type Item = FluentBundleResult; + + fn next(&mut self) -> Option { + let locale = self.locales.next()?; + let res_path_scheme = self + .res_path_scheme + .as_str() + .replace("{locale}", &locale.to_string()); + let mut bundle = FluentBundle::new(vec![locale]); + + let mut errors = vec![]; + + for res_id in &self.res_ids { + let res_path = res_path_scheme.as_str().replace("{res_id}", res_id); + let source = fs::read_to_string(res_path).unwrap(); + let res = match FluentResource::try_new(source) { + Ok(res) => res, + Err((res, err)) => { + errors.extend(err.into_iter().map(Into::into)); + res + } + }; + bundle.add_resource(res).unwrap(); + } + + if errors.is_empty() { + Some(Ok(bundle)) + } else { + Some(Err((bundle, errors))) + } + } +} + +impl futures::Stream for BundleIter { + type Item = FluentBundleResult; + + fn poll_next( + self: std::pin::Pin<&mut Self>, + _cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + todo!() + } +} + +impl BundleGenerator for Bundles { + type Resource = FluentResource; + type LocalesIter = std::vec::IntoIter; + type Iter = BundleIter; + type Stream = BundleIter; + + fn bundles_iter(&self, locales: Self::LocalesIter, res_ids: Vec) -> Self::Iter { + BundleIter { + res_path_scheme: self.res_path_scheme.to_string_lossy().to_string(), + locales, + res_ids, + } + } +} diff --git a/third_party/rust/fluent-fallback/src/bundles.rs b/third_party/rust/fluent-fallback/src/bundles.rs new file mode 100644 index 000000000000..1a5d188e7be7 --- /dev/null +++ b/third_party/rust/fluent-fallback/src/bundles.rs @@ -0,0 +1,425 @@ +use crate::{ + cache::{AsyncCache, Cache}, + env::LocalesProvider, + errors::LocalizationError, + generator::{BundleGenerator, BundleIterator, BundleStream}, + types::{L10nAttribute, L10nKey, L10nMessage}, +}; +use fluent_bundle::{FluentArgs, FluentBundle, FluentError}; +use std::borrow::Cow; + +pub enum BundlesInner +where + G: BundleGenerator, +{ + Iter(Cache), + Stream(AsyncCache), +} + +pub struct Bundles(BundlesInner) +where + G: BundleGenerator; + +impl Bundles +where + G: BundleGenerator, + G::Iter: BundleIterator, +{ + pub fn prefetch_sync(&self) { + match &self.0 { + BundlesInner::Iter(iter) => iter.prefetch(), + BundlesInner::Stream(_) => panic!("Can't prefetch a sync bundle set asynchronously"), + } + } +} + +impl Bundles +where + G: BundleGenerator, + G::Stream: BundleStream, +{ + pub async fn prefetch_async(&self) { + match &self.0 { + BundlesInner::Iter(_) => panic!("Can't prefetch a async bundle set synchronously"), + BundlesInner::Stream(stream) => stream.prefetch().await, + } + } +} + +impl Bundles +where + G: BundleGenerator, +{ + pub fn new

(sync: bool, res_ids: Vec, generator: &G, provider: &P) -> Self + where + G: BundleGenerator, + P: LocalesProvider, + { + Self(if sync { + BundlesInner::Iter(Cache::new( + generator.bundles_iter(provider.locales(), res_ids), + )) + } else { + BundlesInner::Stream(AsyncCache::new( + generator.bundles_stream(provider.locales(), res_ids), + )) + }) + } + + pub async fn format_value<'l>( + &'l self, + id: &'l str, + args: Option<&'l FluentArgs<'_>>, + errors: &mut Vec, + ) -> Option> { + match &self.0 { + BundlesInner::Iter(cache) => Self::format_value_from_iter(cache, id, args, errors), + BundlesInner::Stream(stream) => { + Self::format_value_from_stream(stream, id, args, errors).await + } + } + } + + pub async fn format_values<'l>( + &'l self, + keys: &'l [L10nKey<'l>], + errors: &mut Vec, + ) -> Vec>> { + match &self.0 { + BundlesInner::Iter(cache) => Self::format_values_from_iter(cache, keys, errors), + BundlesInner::Stream(stream) => { + Self::format_values_from_stream(stream, keys, errors).await + } + } + } + + pub async fn format_messages<'l>( + &'l self, + keys: &'l [L10nKey<'l>], + errors: &mut Vec, + ) -> Vec>> { + match &self.0 { + BundlesInner::Iter(cache) => Self::format_messages_from_iter(cache, keys, errors), + BundlesInner::Stream(stream) => { + Self::format_messages_from_stream(stream, keys, errors).await + } + } + } + + pub fn format_value_sync<'l>( + &'l self, + id: &'l str, + args: Option<&'l FluentArgs>, + errors: &mut Vec, + ) -> Result>, LocalizationError> { + match &self.0 { + BundlesInner::Iter(cache) => Ok(Self::format_value_from_iter(cache, id, args, errors)), + BundlesInner::Stream(_) => Err(LocalizationError::SyncRequestInAsyncMode), + } + } + + pub fn format_values_sync<'l>( + &'l self, + keys: &'l [L10nKey<'l>], + errors: &mut Vec, + ) -> Result>>, LocalizationError> { + match &self.0 { + BundlesInner::Iter(cache) => Ok(Self::format_values_from_iter(cache, keys, errors)), + BundlesInner::Stream(_) => Err(LocalizationError::SyncRequestInAsyncMode), + } + } + + pub fn format_messages_sync<'l>( + &'l self, + keys: &'l [L10nKey<'l>], + errors: &mut Vec, + ) -> Result>>, LocalizationError> { + match &self.0 { + BundlesInner::Iter(cache) => Ok(Self::format_messages_from_iter(cache, keys, errors)), + BundlesInner::Stream(_) => Err(LocalizationError::SyncRequestInAsyncMode), + } + } +} + +macro_rules! format_value_from_inner { + ($step:expr, $id:expr, $args:expr, $errors:expr) => { + let mut found_message = false; + + while let Some(bundle) = $step { + let bundle = bundle.as_ref().unwrap_or_else(|(bundle, err)| { + $errors.extend(err.iter().cloned().map(Into::into)); + bundle + }); + + if let Some(msg) = bundle.get_message($id) { + found_message = true; + if let Some(value) = msg.value() { + let mut format_errors = vec![]; + let result = bundle.format_pattern(value, $args, &mut format_errors); + if !format_errors.is_empty() { + $errors.push(LocalizationError::Resolver { + id: $id.to_string(), + locale: bundle.locales[0].clone(), + errors: format_errors, + }); + } + return Some(result); + } else { + $errors.push(LocalizationError::MissingValue { + id: $id.to_string(), + locale: Some(bundle.locales[0].clone()), + }); + } + } else { + $errors.push(LocalizationError::MissingMessage { + id: $id.to_string(), + locale: Some(bundle.locales[0].clone()), + }); + } + } + if found_message { + $errors.push(LocalizationError::MissingValue { + id: $id.to_string(), + locale: None, + }); + } else { + $errors.push(LocalizationError::MissingMessage { + id: $id.to_string(), + locale: None, + }); + } + return None; + }; +} + +#[derive(Clone)] +enum Value<'l> { + Value(Cow<'l, str>), + MissingValue, + None, +} + +macro_rules! format_values_from_inner { + ($step:expr, $keys:expr, $errors:expr) => { + let mut cells = vec![Value::None; $keys.len()]; + + while let Some(bundle) = $step { + let bundle = bundle.as_ref().unwrap_or_else(|(bundle, err)| { + $errors.extend(err.iter().cloned().map(Into::into)); + bundle + }); + + let mut has_missing = false; + + for (key, cell) in $keys + .iter() + .zip(&mut cells) + .filter(|(_, cell)| !matches!(cell, Value::Value(_))) + { + if let Some(msg) = bundle.get_message(&key.id) { + if let Some(value) = msg.value() { + let mut format_errors = vec![]; + *cell = Value::Value(bundle.format_pattern( + value, + key.args.as_ref(), + &mut format_errors, + )); + if !format_errors.is_empty() { + $errors.push(LocalizationError::Resolver { + id: key.id.to_string(), + locale: bundle.locales[0].clone(), + errors: format_errors, + }); + } + } else { + *cell = Value::MissingValue; + has_missing = true; + $errors.push(LocalizationError::MissingValue { + id: key.id.to_string(), + locale: Some(bundle.locales[0].clone()), + }); + } + } else { + has_missing = true; + $errors.push(LocalizationError::MissingMessage { + id: key.id.to_string(), + locale: Some(bundle.locales[0].clone()), + }); + } + } + if !has_missing { + break; + } + } + + return $keys + .iter() + .zip(cells) + .map(|(key, value)| match value { + Value::Value(value) => Some(value), + Value::MissingValue => { + $errors.push(LocalizationError::MissingValue { + id: key.id.to_string(), + locale: None, + }); + None + } + Value::None => { + $errors.push(LocalizationError::MissingMessage { + id: key.id.to_string(), + locale: None, + }); + None + } + }) + .collect(); + }; +} + +macro_rules! format_messages_from_inner { + ($step:expr, $keys:expr, $errors:expr) => { + let mut result = vec![None; $keys.len()]; + + let mut is_complete = false; + + while let Some(bundle) = $step { + let bundle = bundle.as_ref().unwrap_or_else(|(bundle, err)| { + $errors.extend(err.iter().cloned().map(Into::into)); + bundle + }); + + let mut has_missing = false; + for (key, cell) in $keys + .iter() + .zip(&mut result) + .filter(|(_, cell)| cell.is_none()) + { + let mut format_errors = vec![]; + let msg = Self::format_message_from_bundle(bundle, key, &mut format_errors); + + if msg.is_none() { + has_missing = true; + $errors.push(LocalizationError::MissingMessage { + id: key.id.to_string(), + locale: Some(bundle.locales[0].clone()), + }); + } else if !format_errors.is_empty() { + $errors.push(LocalizationError::Resolver { + id: key.id.to_string(), + locale: bundle.locales.get(0).cloned().unwrap(), + errors: format_errors, + }); + } + + *cell = msg; + } + if !has_missing { + is_complete = true; + break; + } + } + + if !is_complete { + for (key, _) in $keys + .iter() + .zip(&mut result) + .filter(|(_, cell)| cell.is_none()) + { + $errors.push(LocalizationError::MissingMessage { + id: key.id.to_string(), + locale: None, + }); + } + } + + return result; + }; +} + +impl Bundles +where + G: BundleGenerator, +{ + fn format_value_from_iter<'l>( + cache: &'l Cache, + id: &'l str, + args: Option<&'l FluentArgs>, + errors: &mut Vec, + ) -> Option> { + let mut bundle_iter = cache.into_iter(); + format_value_from_inner!(bundle_iter.next(), id, args, errors); + } + + async fn format_value_from_stream<'l>( + stream: &'l AsyncCache, + id: &'l str, + args: Option<&'l FluentArgs<'_>>, + errors: &mut Vec, + ) -> Option> { + use futures::StreamExt; + + let mut bundle_stream = stream.stream(); + format_value_from_inner!(bundle_stream.next().await, id, args, errors); + } + + async fn format_messages_from_stream<'l>( + stream: &'l AsyncCache, + keys: &'l [L10nKey<'l>], + errors: &mut Vec, + ) -> Vec>> { + use futures::StreamExt; + let mut bundle_stream = stream.stream(); + format_messages_from_inner!(bundle_stream.next().await, keys, errors); + } + + async fn format_values_from_stream<'l>( + stream: &'l AsyncCache, + keys: &'l [L10nKey<'l>], + errors: &mut Vec, + ) -> Vec>> { + use futures::StreamExt; + let mut bundle_stream = stream.stream(); + + format_values_from_inner!(bundle_stream.next().await, keys, errors); + } + + fn format_message_from_bundle<'l>( + bundle: &'l FluentBundle, + key: &'l L10nKey, + format_errors: &mut Vec, + ) -> Option> { + let msg = bundle.get_message(&key.id)?; + let value = msg + .value() + .map(|pattern| bundle.format_pattern(pattern, key.args.as_ref(), format_errors)); + let attributes = msg + .attributes() + .map(|attr| { + let value = bundle.format_pattern(attr.value(), key.args.as_ref(), format_errors); + L10nAttribute { + name: attr.id().into(), + value, + } + }) + .collect(); + Some(L10nMessage { value, attributes }) + } + + fn format_messages_from_iter<'l>( + cache: &'l Cache, + keys: &'l [L10nKey<'l>], + errors: &mut Vec, + ) -> Vec>> { + let mut bundle_iter = cache.into_iter(); + format_messages_from_inner!(bundle_iter.next(), keys, errors); + } + + fn format_values_from_iter<'l>( + cache: &'l Cache, + keys: &'l [L10nKey<'l>], + errors: &mut Vec, + ) -> Vec>> { + let mut bundle_iter = cache.into_iter(); + format_values_from_inner!(bundle_iter.next(), keys, errors); + } +} diff --git a/third_party/rust/fluent-fallback/src/cache.rs b/third_party/rust/fluent-fallback/src/cache.rs new file mode 100644 index 000000000000..32bc33fad1b3 --- /dev/null +++ b/third_party/rust/fluent-fallback/src/cache.rs @@ -0,0 +1,253 @@ +use std::{ + cell::{RefCell, UnsafeCell}, + cmp::Ordering, + pin::Pin, + task::Context, + task::Poll, + task::Waker, +}; + +use crate::generator::{BundleIterator, BundleStream}; +use crate::pin_cell::{PinCell, PinMut}; +use chunky_vec::ChunkyVec; +use futures::{ready, Stream}; + +pub struct Cache +where + I: Iterator, +{ + iter: RefCell, + items: UnsafeCell>, + res: std::marker::PhantomData, +} + +impl Cache +where + I: Iterator, +{ + pub fn new(iter: I) -> Self { + Self { + iter: RefCell::new(iter), + items: Default::default(), + res: std::marker::PhantomData, + } + } + + pub fn len(&self) -> usize { + unsafe { + let items = self.items.get(); + (*items).len() + } + } + + pub fn get(&self, index: usize) -> Option<&I::Item> { + unsafe { + let items = self.items.get(); + (*items).get(index) + } + } + + /// Push, immediately getting a reference to the element + pub fn push_get(&self, new_value: I::Item) -> &I::Item { + unsafe { + let items = self.items.get(); + (*items).push_get(new_value) + } + } +} + +impl Cache +where + I: BundleIterator + Iterator, +{ + pub fn prefetch(&self) { + self.iter.borrow_mut().prefetch_sync(); + } +} + +pub struct CacheIter<'a, I, R> +where + I: Iterator, +{ + cache: &'a Cache, + curr: usize, +} + +impl<'a, I, R> Iterator for CacheIter<'a, I, R> +where + I: Iterator, +{ + type Item = &'a I::Item; + + fn next(&mut self) -> Option { + let cache_len = self.cache.len(); + match self.curr.cmp(&cache_len) { + Ordering::Less => { + // Cached value + self.curr += 1; + self.cache.get(self.curr - 1) + } + Ordering::Equal => { + // Get the next item from the iterator + let item = self.cache.iter.borrow_mut().next(); + self.curr += 1; + if let Some(item) = item { + Some(self.cache.push_get(item)) + } else { + None + } + } + Ordering::Greater => { + // Ran off the end of the cache + None + } + } + } +} + +impl<'a, I, R> IntoIterator for &'a Cache +where + I: Iterator, +{ + type Item = &'a I::Item; + type IntoIter = CacheIter<'a, I, R>; + + fn into_iter(self) -> Self::IntoIter { + CacheIter { + cache: self, + curr: 0, + } + } +} + +//////////////////////////////////////////////////////////////////////////////// + +pub struct AsyncCache +where + S: Stream, +{ + stream: PinCell, + items: UnsafeCell>, + // TODO: Should probably be an SmallVec<[Waker; 1]> or something? I guess + // multiple pending wakes are not really all that common. + pending_wakes: RefCell>, + res: std::marker::PhantomData, +} + +impl AsyncCache +where + S: Stream, +{ + pub fn new(stream: S) -> Self { + Self { + stream: PinCell::new(stream), + items: Default::default(), + pending_wakes: Default::default(), + res: std::marker::PhantomData, + } + } + + pub fn len(&self) -> usize { + unsafe { + let items = self.items.get(); + (*items).len() + } + } + + pub fn get(&self, index: usize) -> Poll> { + unsafe { + let items = self.items.get(); + (*items).get(index).into() + } + } + + /// Push, immediately getting a reference to the element + pub fn push_get(&self, new_value: S::Item) -> &S::Item { + unsafe { + let items = self.items.get(); + (*items).push_get(new_value) + } + } + + pub fn stream(&self) -> AsyncCacheStream<'_, S, R> { + AsyncCacheStream { + cache: self, + curr: 0, + } + } +} + +impl AsyncCache +where + S: BundleStream + Stream, +{ + pub async fn prefetch(&self) { + let pin = unsafe { Pin::new_unchecked(&self.stream) }; + unsafe { PinMut::as_mut(&mut pin.borrow_mut()).get_unchecked_mut() } + .prefetch_async() + .await + } +} + +impl AsyncCache +where + S: Stream, +{ + // Helper function that gets the next value from wrapped stream. + fn poll_next_item(&self, cx: &mut Context<'_>) -> Poll> { + let pin = unsafe { Pin::new_unchecked(&self.stream) }; + let poll = PinMut::as_mut(&mut pin.borrow_mut()).poll_next(cx); + if poll.is_ready() { + let wakers = std::mem::take(&mut *self.pending_wakes.borrow_mut()); + for waker in wakers { + waker.wake(); + } + } else { + self.pending_wakes.borrow_mut().push(cx.waker().clone()); + } + poll + } +} + +pub struct AsyncCacheStream<'a, S, R> +where + S: Stream, +{ + cache: &'a AsyncCache, + curr: usize, +} + +impl<'a, S, R> Stream for AsyncCacheStream<'a, S, R> +where + S: Stream, +{ + type Item = &'a S::Item; + + fn poll_next( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + let cache_len = self.cache.len(); + match self.curr.cmp(&cache_len) { + Ordering::Less => { + // Cached value + self.curr += 1; + self.cache.get(self.curr - 1) + } + Ordering::Equal => { + // Get the next item from the stream + let item = ready!(self.cache.poll_next_item(cx)); + self.curr += 1; + if let Some(item) = item { + Some(self.cache.push_get(item)).into() + } else { + None.into() + } + } + Ordering::Greater => { + // Ran off the end of the cache + None.into() + } + } + } +} diff --git a/third_party/rust/fluent-fallback/src/env.rs b/third_party/rust/fluent-fallback/src/env.rs new file mode 100644 index 000000000000..cf340fcfdf06 --- /dev/null +++ b/third_party/rust/fluent-fallback/src/env.rs @@ -0,0 +1,84 @@ +//! Traits required to provide environment driven data for [`Localization`](crate::Localization). +//! +//! Since [`Localization`](crate::Localization) is a long-lived structure, +//! the model in which the user provides ability for the system to react to changes +//! is by implementing the given environmental trait and triggering +//! [`Localization::on_change`](crate::Localization::on_change) method. +//! +//! At the moment just a single trait is provided, which allows the +//! environment to feed a selection of locales to be provided to the instance. +//! +//! The locales provided to [`Localization`](crate::Localization) should be +//! already negotiated to ensure that the resources in those locales +//! are available. The list should also be sorted according to the user +//! preference, as the order is significant for how [`Localization`](crate::Localization) performs +//! fallbacking. +use unic_langid::LanguageIdentifier; + +/// A trait used to provide a selection of locales to be used by the +/// [`Localization`](crate::Localization) instance for runtime +/// locale fallbacking. +/// +/// # Example +/// ``` +/// use fluent_fallback::{Localization, env::LocalesProvider}; +/// use fluent_resmgr::ResourceManager; +/// use unic_langid::LanguageIdentifier; +/// use std::{ +/// rc::Rc, +/// cell::RefCell +/// }; +/// +/// #[derive(Clone)] +/// struct Env { +/// locales: Rc>>, +/// } +/// +/// impl Env { +/// pub fn new(locales: Vec) -> Self { +/// Self { locales: Rc::new(RefCell::new(locales)) } +/// } +/// +/// pub fn set_locales(&mut self, new_locales: Vec) { +/// let mut locales = self.locales.borrow_mut(); +/// locales.clear(); +/// locales.extend(new_locales); +/// } +/// } +/// +/// impl LocalesProvider for Env { +/// type Iter = as IntoIterator>::IntoIter; +/// fn locales(&self) -> Self::Iter { +/// self.locales.borrow().clone().into_iter() +/// } +/// } +/// +/// let res_mgr = ResourceManager::new("./path/{locale}/".to_string()); +/// +/// let mut env = Env::new(vec![ +/// "en-GB".parse().unwrap() +/// ]); +/// +/// let mut loc = Localization::with_env(vec![], true, env.clone(), res_mgr); +/// +/// env.set_locales(vec![ +/// "de".parse().unwrap(), +/// "en-GB".parse().unwrap(), +/// ]); +/// +/// loc.on_change(); +/// +/// // The next format call will attempt to localize to `de` first and +/// // fallback on `en-GB`. +/// ``` +pub trait LocalesProvider { + type Iter: Iterator; + fn locales(&self) -> Self::Iter; +} + +impl LocalesProvider for Vec { + type Iter = as IntoIterator>::IntoIter; + fn locales(&self) -> Self::Iter { + self.clone().into_iter() + } +} diff --git a/third_party/rust/fluent-fallback/src/errors.rs b/third_party/rust/fluent-fallback/src/errors.rs new file mode 100644 index 000000000000..8f1810ab3224 --- /dev/null +++ b/third_party/rust/fluent-fallback/src/errors.rs @@ -0,0 +1,71 @@ +use fluent_bundle::FluentError; +use std::error::Error; +use unic_langid::LanguageIdentifier; + +#[derive(Debug, PartialEq)] +pub enum LocalizationError { + Bundle { + error: FluentError, + }, + Resolver { + id: String, + locale: LanguageIdentifier, + errors: Vec, + }, + MissingMessage { + id: String, + locale: Option, + }, + MissingValue { + id: String, + locale: Option, + }, + SyncRequestInAsyncMode, +} + +impl From for LocalizationError { + fn from(error: FluentError) -> Self { + Self::Bundle { error } + } +} + +impl std::fmt::Display for LocalizationError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Bundle { error } => write!(f, "[fluent][bundle] error: {}", error), + Self::Resolver { id, locale, errors } => { + let errors: Vec = errors.iter().map(|err| err.to_string()).collect(); + write!( + f, + "[fluent][resolver] errors in {}/{}: {}", + locale.to_string(), + id, + errors.join(", ") + ) + } + Self::MissingMessage { + id, + locale: Some(locale), + } => write!(f, "[fluent] Missing message in locale {}: {}", locale, id), + Self::MissingMessage { id, locale: None } => { + write!(f, "[fluent] Couldn't find a message: {}", id) + } + Self::MissingValue { + id, + locale: Some(locale), + } => write!( + f, + "[fluent] Message has no value in locale {}: {}", + locale, id + ), + Self::MissingValue { id, locale: None } => { + write!(f, "[fluent] Couldn't find a message with value: {}", id) + } + Self::SyncRequestInAsyncMode => { + write!(f, "Triggered synchronous format while in async mode") + } + } + } +} + +impl Error for LocalizationError {} diff --git a/third_party/rust/fluent-fallback/src/generator.rs b/third_party/rust/fluent-fallback/src/generator.rs new file mode 100644 index 000000000000..092043041f9a --- /dev/null +++ b/third_party/rust/fluent-fallback/src/generator.rs @@ -0,0 +1,30 @@ +use fluent_bundle::{FluentBundle, FluentError, FluentResource}; +use futures::Stream; +use std::borrow::Borrow; +use unic_langid::LanguageIdentifier; + +pub type FluentBundleResult = Result, (FluentBundle, Vec)>; + +pub trait BundleIterator { + fn prefetch_sync(&mut self) {} +} + +#[async_trait::async_trait(?Send)] +pub trait BundleStream { + async fn prefetch_async(&mut self) {} +} + +pub trait BundleGenerator { + type Resource: Borrow; + type LocalesIter: Iterator; + type Iter: Iterator>; + type Stream: Stream>; + + fn bundles_iter(&self, _locales: Self::LocalesIter, _res_ids: Vec) -> Self::Iter { + unimplemented!(); + } + + fn bundles_stream(&self, _locales: Self::LocalesIter, _res_ids: Vec) -> Self::Stream { + unimplemented!(); + } +} diff --git a/third_party/rust/fluent-fallback/src/lib.rs b/third_party/rust/fluent-fallback/src/lib.rs new file mode 100644 index 000000000000..83a34afa2830 --- /dev/null +++ b/third_party/rust/fluent-fallback/src/lib.rs @@ -0,0 +1,104 @@ +//! Fluent is a modern localization system designed to improve how software is translated. +//! +//! `fluent-fallback` is a high-level component of the [Fluent Localization +//! System](https://www.projectfluent.org). +//! +//! The crate builds on top of the mid-level [`fluent-bundle`](../fluent-bundle) package, and provides an ergonomic API for highly flexible localization. +//! +//! The functionality of this level is complete, but the API itself is in the +//! early stages and the goal of being ergonomic is yet to be achieved. +//! +//! If the user is willing to work through the challenge of setting up the +//! boiler-plate that will eventually go away, `fluent-fallback` provides +//! a powerful abstraction around [`FluentBundle`](fluent_bundle::FluentBundle) coupled +//! with a localization resource management system. +//! +//! The main struct, [`Localization`], is a long-lived, reactive, multi-lingual +//! struct which allows for strong error recovery and locale +//! fallbacking, exposing synchronous and asynchronous ergonomic methods +//! for [`L10nMessage`](types::L10nMessage) retrieval. +//! +//! [`Localization`] is also an API that is to be used when designing bindings +//! to user interface systems, such as DOM, React, and others. +//! +//! # Example +//! +//! ``` +//! use fluent_fallback::Localization; +//! use fluent_resmgr::ResourceManager; +//! use unic_langid::langid; +//! +//! let res_mgr = ResourceManager::new("./tests/resources/{locale}/".to_string()); +//! +//! let loc = Localization::with_env( +//! vec![ +//! "test.ftl".to_string(), +//! "test2.ftl".to_string() +//! ], +//! true, +//! vec![langid!("en-US")], +//! res_mgr, +//! ); +//! let bundles = loc.bundles(); +//! +//! let mut errors = vec![]; +//! let value = bundles.format_value_sync("hello-world", None, &mut errors) +//! .expect("Failed to format a value"); +//! +//! assert_eq!(value, Some("Hello World [en]".into())); +//! ``` +//! +//! The above example is far from the ergonomical API style the Fluent project +//! is aiming for, but it represents the full scope of functionality intended +//! for the model. +//! +//! # Resource Management +//! +//! Resource management is one of the most complicated parts of a localization system. +//! In particular, modern software may have needs for both synchronous +//! and asynchronous I/O. That, in turn has a large impact on what can happen +//! in case of missing resources, or errors. +//! +//! Currently, [`Localization`] can be specialized over an implementation of +//! [`generator::BundleGenerator`] trait which provides a method to generate an +//! [`Iterator`] and [`Stream`](futures::stream::Stream). +//! +//! This is not very elegant and will likely be improved in the future, but for the time being, if +//! the customer doesn't need one of the modes, the unnecessary method should use the +//! `unimplemented!()` macro as its body. +//! +//! `fluent-resmgr` provides a simple resource manager which handles synchronous I/O +//! and uses local file system to store resources in a directory structure. +//! +//! That model is often sufficient and the user can either use `fluent-resmgr` or write +//! a similar API to provide the generator for [`Localization`]. +//! +//! Alternatively, a much more sophisticated resource manager can be used. Mozilla +//! for its needs in Firefox uses [`L10nRegistry`](https://github.com/zbraniecki/l10nregistry-rs) +//! library which implements [`BundleGenerator`](generator::BundleGenerator). +//! +//! # Locale Management +//! +//! As a long lived structure, the [`Localization`] is intended to handle runtime locale +//! management. +//! +//! In the example above, [`Vec`](unic_langid::LanguageIdentifier) +//! provides a static list of locales that the [`Localization`] handles, but that's just the +//! simplest implementation of the [`env::LocalesProvider`], and one can implement +//! a much more sophisticated one that reacts to user or environment driven changes, and +//! called [`Localization::on_change`] to trigger a new locales to be used for the +//! next translation request. +//! +//! See [`env::LocalesProvider`] trait for an example of a reactive system implementation. +mod bundles; +mod cache; +pub mod env; +mod errors; +pub mod generator; +mod localization; +mod pin_cell; +pub mod types; + +pub use bundles::Bundles; +pub use errors::LocalizationError; +pub use localization::Localization; diff --git a/third_party/rust/fluent-fallback/src/localization.rs b/third_party/rust/fluent-fallback/src/localization.rs new file mode 100644 index 000000000000..5f8279d99a41 --- /dev/null +++ b/third_party/rust/fluent-fallback/src/localization.rs @@ -0,0 +1,129 @@ +use crate::{ + bundles::Bundles, + env::LocalesProvider, + generator::{BundleGenerator, BundleIterator, BundleStream}, +}; +use once_cell::sync::OnceCell; +use std::rc::Rc; + +pub struct Localization +where + G: BundleGenerator, + P: LocalesProvider, +{ + bundles: OnceCell>>, + generator: G, + provider: P, + sync: bool, + res_ids: Vec, +} + +impl Localization +where + G: BundleGenerator + Default, + P: LocalesProvider + Default, +{ + pub fn new(res_ids: Vec, sync: bool) -> Self { + Self { + bundles: OnceCell::new(), + generator: G::default(), + provider: P::default(), + sync, + res_ids, + } + } +} + +impl Localization +where + G: BundleGenerator, + P: LocalesProvider, +{ + pub fn with_env(res_ids: Vec, sync: bool, provider: P, generator: G) -> Self { + Self { + bundles: OnceCell::new(), + generator, + provider, + sync, + res_ids, + } + } + + pub fn is_sync(&self) -> bool { + self.sync + } + + pub fn add_resource_id(&mut self, res_id: String) { + self.res_ids.push(res_id); + self.on_change(); + } + + pub fn add_resource_ids(&mut self, res_ids: Vec) { + self.res_ids.extend(res_ids); + self.on_change(); + } + + pub fn remove_resource_id(&mut self, res_id: String) -> usize { + self.res_ids.retain(|x| *x != res_id); + self.on_change(); + self.res_ids.len() + } + + pub fn remove_resource_ids(&mut self, res_ids: Vec) -> usize { + self.res_ids.retain(|x| !res_ids.contains(x)); + self.on_change(); + self.res_ids.len() + } + + pub fn set_async(&mut self) { + if self.sync { + self.sync = false; + self.on_change(); + } + } + + pub fn on_change(&mut self) { + self.bundles.take(); + } +} + +impl Localization +where + G: BundleGenerator, + G::Iter: BundleIterator, + P: LocalesProvider, +{ + pub fn prefetch_sync(&mut self) { + let bundles = self.bundles(); + bundles.prefetch_sync(); + } +} + +impl Localization +where + G: BundleGenerator, + G::Stream: BundleStream, + P: LocalesProvider, +{ + pub async fn prefetch_async(&mut self) { + let bundles = self.bundles(); + bundles.prefetch_async().await + } +} + +impl Localization +where + G: BundleGenerator, + P: LocalesProvider, +{ + pub fn bundles(&self) -> &Rc> { + self.bundles.get_or_init(|| { + Rc::new(Bundles::new( + self.sync, + self.res_ids.clone(), + &self.generator, + &self.provider, + )) + }) + } +} diff --git a/third_party/rust/fluent-fallback/src/pin_cell/README.md b/third_party/rust/fluent-fallback/src/pin_cell/README.md new file mode 100644 index 000000000000..b1c475f51bf9 --- /dev/null +++ b/third_party/rust/fluent-fallback/src/pin_cell/README.md @@ -0,0 +1,2 @@ +This is a temporary fork of https://github.com/withoutboats/pin-cell until +https://github.com/withoutboats/pin-cell/issues/6 gets resolved. diff --git a/third_party/rust/fluent-fallback/src/pin_cell/mod.rs b/third_party/rust/fluent-fallback/src/pin_cell/mod.rs new file mode 100644 index 000000000000..175f9677e042 --- /dev/null +++ b/third_party/rust/fluent-fallback/src/pin_cell/mod.rs @@ -0,0 +1,97 @@ +#![deny(missing_docs, missing_debug_implementations)] +//! This library defines the `PinCell` type, a pinning variant of the standard +//! library's `RefCell`. +//! +//! It is not safe to "pin project" through a `RefCell` - getting a pinned +//! reference to something inside the `RefCell` when you have a pinned +//! refernece to the `RefCell` - because `RefCell` is too powerful. +//! +//! A `PinCell` is slightly less powerful than `RefCell`: unlike a `RefCell`, +//! one cannot get a mutable reference into a `PinCell`, only a pinned mutable +//! reference (`Pin<&mut T>`). This makes pin projection safe, allowing you +//! to use interior mutability with the knowledge that `T` will never actually +//! be moved out of the `RefCell` that wraps it. + +mod pin_mut; +mod pin_ref; + +use core::cell::{BorrowMutError, RefCell, RefMut}; +use core::pin::Pin; + +pub use pin_mut::PinMut; +pub use pin_ref::PinRef; + +/// A mutable memory location with dynamically checked borrow rules +/// +/// Unlike `RefCell`, this type only allows *pinned* mutable access to the +/// inner value, enabling a "pin-safe" version of interior mutability. +/// +/// See the standard library documentation for more information. +#[derive(Default, Clone, Ord, PartialOrd, Eq, PartialEq, Debug)] +pub struct PinCell { + inner: RefCell, +} + +impl PinCell { + /// Creates a new `PinCell` containing `value`. + pub const fn new(value: T) -> PinCell { + PinCell { + inner: RefCell::new(value), + } + } +} + +impl PinCell { + /// Mutably borrows the wrapped value, preserving its pinnedness. + /// + /// The borrow lasts until the returned `PinMut` or all `PinMut`s derived + /// from it exit scope. The value cannot be borrowed while this borrow is + /// active. + pub fn borrow_mut(self: Pin<&Self>) -> PinMut<'_, T> { + self.try_borrow_mut().expect("already borrowed") + } + + /// Mutably borrows the wrapped value, preserving its pinnedness, + /// returning an error if the value is currently borrowed. + /// + /// The borrow lasts until the returned `PinMut` or all `PinMut`s derived + /// from it exit scope. The value cannot be borrowed while this borrow is + /// active. + /// + /// This is the non-panicking variant of `borrow_mut`. + pub fn try_borrow_mut<'a>(self: Pin<&'a Self>) -> Result, BorrowMutError> { + let ref_mut: RefMut<'a, T> = Pin::get_ref(self).inner.try_borrow_mut()?; + + // this is a pin projection from Pin<&PinCell> to Pin> + // projecting is safe because: + // + // - for (PinCell: Unpin) imples (RefMut: Unpin) + // holds true + // - PinCell does not implement Drop + // + // see discussion on tracking issue #49150 about pin projection + // invariants + let pin_ref_mut: Pin> = unsafe { Pin::new_unchecked(ref_mut) }; + + Ok(PinMut { inner: pin_ref_mut }) + } +} + +impl From for PinCell { + fn from(value: T) -> PinCell { + PinCell::new(value) + } +} + +impl From> for PinCell { + fn from(cell: RefCell) -> PinCell { + PinCell { inner: cell } + } +} + +impl From> for RefCell { + fn from(input: PinCell) -> Self { + input.inner + } +} +// TODO CoerceUnsized diff --git a/third_party/rust/fluent-fallback/src/pin_cell/pin_mut.rs b/third_party/rust/fluent-fallback/src/pin_cell/pin_mut.rs new file mode 100644 index 000000000000..09a4d4a6fbe5 --- /dev/null +++ b/third_party/rust/fluent-fallback/src/pin_cell/pin_mut.rs @@ -0,0 +1,50 @@ +use core::cell::RefMut; +use core::fmt; +use core::ops::Deref; +use core::pin::Pin; + +#[derive(Debug)] +/// A wrapper type for a mutably borrowed value from a `PinCell`. +pub struct PinMut<'a, T: ?Sized> { + pub(crate) inner: Pin>, +} + +impl<'a, T: ?Sized> Deref for PinMut<'a, T> { + type Target = T; + + fn deref(&self) -> &T { + &*self.inner + } +} + +impl<'a, T: ?Sized> PinMut<'a, T> { + /// Get a pinned mutable reference to the value inside this wrapper. + pub fn as_mut<'b>(orig: &'b mut PinMut<'a, T>) -> Pin<&'b mut T> { + orig.inner.as_mut() + } +} + +/* TODO implement these APIs + +impl<'a, T: ?Sized> PinMut<'a, T> { + pub fn map(orig: PinMut<'a, T>, f: F) -> PinMut<'a, U> where + F: FnOnce(Pin<&mut T>) -> Pin<&mut U>, + { + panic!() + } + + pub fn map_split(orig: PinMut<'a, T>, f: F) -> (PinMut<'a, U>, PinMut<'a, V>) where + F: FnOnce(Pin<&mut T>) -> (Pin<&mut U>, Pin<&mut V>) + { + panic!() + } +} +*/ + +impl<'a, T: fmt::Display + ?Sized> fmt::Display for PinMut<'a, T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + ::fmt(&**self, f) + } +} + +// TODO CoerceUnsized diff --git a/third_party/rust/fluent-fallback/src/pin_cell/pin_ref.rs b/third_party/rust/fluent-fallback/src/pin_cell/pin_ref.rs new file mode 100644 index 000000000000..46f9cfdabb7f --- /dev/null +++ b/third_party/rust/fluent-fallback/src/pin_cell/pin_ref.rs @@ -0,0 +1,47 @@ +use core::cell::Ref; +use core::fmt; +use core::ops::Deref; +use core::pin::Pin; + +#[derive(Debug)] +/// A wrapper type for a immutably borrowed value from a `PinCell`. +pub struct PinRef<'a, T: ?Sized> { + pub(crate) inner: Pin>, +} + +impl<'a, T: ?Sized> Deref for PinRef<'a, T> { + type Target = T; + + fn deref(&self) -> &T { + &*self.inner + } +} + +/* TODO implement these APIs + +impl<'a, T: ?Sized> PinRef<'a, T> { + pub fn clone(orig: &PinRef<'a, T>) -> PinRef<'a, T> { + panic!() + } + + pub fn map(orig: PinRef<'a, T>, f: F) -> PinRef<'a, U> where + F: FnOnce(Pin<&T>) -> Pin<&U>, + { + panic!() + } + + pub fn map_split(orig: PinRef<'a, T>, f: F) -> (PinRef<'a, U>, PinRef<'a, V>) where + F: FnOnce(Pin<&T>) -> (Pin<&U>, Pin<&V>) + { + panic!() + } +} +*/ + +impl<'a, T: fmt::Display + ?Sized> fmt::Display for PinRef<'a, T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + ::fmt(&**self, f) + } +} + +// TODO CoerceUnsized diff --git a/third_party/rust/fluent-fallback/src/types.rs b/third_party/rust/fluent-fallback/src/types.rs new file mode 100644 index 000000000000..07dc248ee0a9 --- /dev/null +++ b/third_party/rust/fluent-fallback/src/types.rs @@ -0,0 +1,29 @@ +use fluent_bundle::FluentArgs; +use std::borrow::Cow; + +#[derive(Debug)] +pub struct L10nKey<'l> { + pub id: Cow<'l, str>, + pub args: Option>, +} + +impl<'l> From<&'l str> for L10nKey<'l> { + fn from(id: &'l str) -> Self { + Self { + id: id.into(), + args: None, + } + } +} + +#[derive(Debug, Clone)] +pub struct L10nAttribute<'l> { + pub name: Cow<'l, str>, + pub value: Cow<'l, str>, +} + +#[derive(Debug, Clone)] +pub struct L10nMessage<'l> { + pub value: Option>, + pub attributes: Vec>, +} diff --git a/third_party/rust/fluent-fallback/tests/localization_test.rs b/third_party/rust/fluent-fallback/tests/localization_test.rs new file mode 100644 index 000000000000..da7b402ad2ff --- /dev/null +++ b/third_party/rust/fluent-fallback/tests/localization_test.rs @@ -0,0 +1,490 @@ +use std::borrow::Cow; +use std::fs; + +use fluent_bundle::{ + resolver::errors::{ReferenceKind, ResolverError}, + FluentArgs, FluentBundle, FluentError, FluentResource, +}; +use fluent_fallback::{ + env::LocalesProvider, + generator::{BundleGenerator, FluentBundleResult}, + types::L10nKey, + Localization, LocalizationError, +}; +use std::cell::RefCell; +use std::rc::Rc; +use unic_langid::{langid, LanguageIdentifier}; + +struct InnerLocales { + locales: RefCell>, +} + +impl InnerLocales { + pub fn insert(&self, index: usize, element: LanguageIdentifier) { + self.locales.borrow_mut().insert(index, element); + } +} + +#[derive(Clone)] +struct Locales { + inner: Rc, +} + +impl Locales { + pub fn new(locales: Vec) -> Self { + Self { + inner: Rc::new(InnerLocales { + locales: RefCell::new(locales), + }), + } + } + + pub fn insert(&mut self, index: usize, element: LanguageIdentifier) { + self.inner.insert(index, element); + } +} + +impl LocalesProvider for Locales { + type Iter = as IntoIterator>::IntoIter; + fn locales(&self) -> Self::Iter { + self.inner.locales.borrow().clone().into_iter() + } +} + +// Due to limitation of trait, we need a nameable Iterator type. Due to the +// lack of GATs, these have to own members instead of taking slices. +struct BundleIter { + locales: as IntoIterator>::IntoIter, + res_ids: Vec, +} + +impl Iterator for BundleIter { + type Item = FluentBundleResult; + + fn next(&mut self) -> Option { + let locale = self.locales.next()?; + + let mut bundle = FluentBundle::new(vec![locale.clone()]); + bundle.set_use_isolating(false); + + let mut errors = vec![]; + + for res_id in &self.res_ids { + let full_path = format!("./tests/resources/{}/{}", locale, res_id); + let source = fs::read_to_string(full_path).unwrap(); + let res = match FluentResource::try_new(source) { + Ok(res) => res, + Err((res, err)) => { + errors.extend(err.into_iter().map(Into::into)); + res + } + }; + bundle.add_resource(res).unwrap(); + } + if errors.is_empty() { + Some(Ok(bundle)) + } else { + Some(Err((bundle, errors))) + } + } +} + +impl futures::Stream for BundleIter { + type Item = FluentBundleResult; + + fn poll_next( + mut self: std::pin::Pin<&mut Self>, + _cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + if let Some(locale) = self.locales.next() { + let mut bundle = FluentBundle::new(vec![locale.clone()]); + bundle.set_use_isolating(false); + + let mut errors = vec![]; + for res_id in &self.res_ids { + let full_path = format!("./tests/resources/{}/{}", locale, res_id); + let source = fs::read_to_string(full_path).unwrap(); + let res = match FluentResource::try_new(source) { + Ok(res) => res, + Err((res, err)) => { + errors.extend(err.into_iter().map(Into::into)); + res + } + }; + bundle.add_resource(res).unwrap(); + } + if errors.is_empty() { + Some(Ok(bundle)).into() + } else { + Some(Err((bundle, errors))).into() + } + } else { + None.into() + } + } +} + +struct ResourceManager; + +impl BundleGenerator for ResourceManager { + type Resource = FluentResource; + type LocalesIter = std::vec::IntoIter; + type Iter = BundleIter; + type Stream = BundleIter; + + fn bundles_iter(&self, locales: Self::LocalesIter, res_ids: Vec) -> Self::Iter { + BundleIter { locales, res_ids } + } + + fn bundles_stream(&self, locales: Self::LocalesIter, res_ids: Vec) -> Self::Stream { + BundleIter { locales, res_ids } + } +} + +#[test] +fn localization_format() { + let resource_ids: Vec = vec!["test.ftl".into(), "test2.ftl".into()]; + let locales = Locales::new(vec![langid!("pl"), langid!("en-US")]); + let res_mgr = ResourceManager; + let mut errors = vec![]; + + let loc = Localization::with_env(resource_ids, true, locales, res_mgr); + let bundles = loc.bundles(); + + let value = bundles + .format_value_sync("hello-world", None, &mut errors) + .unwrap(); + assert_eq!(value, Some(Cow::Borrowed("Hello World [pl]"))); + + let value = bundles + .format_value_sync("missing-message", None, &mut errors) + .unwrap(); + assert_eq!(value, None); + + let value = bundles + .format_value_sync("hello-world-3", None, &mut errors) + .unwrap(); + assert_eq!(value, Some(Cow::Borrowed("Hello World 3 [en]"))); + + assert_eq!(errors.len(), 4); +} + +#[test] +fn localization_on_change() { + let resource_ids: Vec = vec!["test.ftl".into(), "test2.ftl".into()]; + + let mut locales = Locales::new(vec![langid!("en-US")]); + let res_mgr = ResourceManager; + let mut errors = vec![]; + + let mut loc = Localization::with_env(resource_ids, true, locales.clone(), res_mgr); + let bundles = loc.bundles(); + + let value = bundles + .format_value_sync("hello-world", None, &mut errors) + .unwrap(); + assert_eq!(value, Some(Cow::Borrowed("Hello World [en]"))); + + locales.insert(0, langid!("pl")); + loc.on_change(); + + let bundles = loc.bundles(); + let value = bundles + .format_value_sync("hello-world", None, &mut errors) + .unwrap(); + assert_eq!(value, Some(Cow::Borrowed("Hello World [pl]"))); +} + +#[test] +fn localization_format_value_missing_errors() { + let resource_ids: Vec = vec!["test.ftl".into(), "test2.ftl".into()]; + + let locales = Locales::new(vec![langid!("pl"), langid!("en-US")]); + let res_mgr = ResourceManager; + let mut errors = vec![]; + + let loc = Localization::with_env(resource_ids, true, locales.clone(), res_mgr); + let bundles = loc.bundles(); + + let _ = bundles + .format_value_sync("missing-message", None, &mut errors) + .unwrap(); + assert_eq!( + errors, + vec![ + LocalizationError::MissingMessage { + id: "missing-message".to_string(), + locale: Some(langid!("pl")) + }, + LocalizationError::MissingMessage { + id: "missing-message".to_string(), + locale: Some(langid!("en-US")) + }, + LocalizationError::MissingMessage { + id: "missing-message".to_string(), + locale: None + }, + ] + ); + + errors.clear(); + + let _ = bundles + .format_value_sync("message-3", None, &mut errors) + .unwrap(); + assert_eq!( + errors, + vec![ + LocalizationError::MissingValue { + id: "message-3".to_string(), + locale: Some(langid!("pl")) + }, + LocalizationError::MissingValue { + id: "message-3".to_string(), + locale: Some(langid!("en-US")) + }, + LocalizationError::MissingValue { + id: "message-3".to_string(), + locale: None + }, + ] + ); +} + +#[test] +fn localization_format_value_sync_missing_errors() { + let resource_ids: Vec = vec!["test.ftl".into(), "test2.ftl".into()]; + + let locales = Locales::new(vec![langid!("pl"), langid!("en-US")]); + let res_mgr = ResourceManager; + let mut errors = vec![]; + + let loc = Localization::with_env(resource_ids, true, locales.clone(), res_mgr); + let bundles = loc.bundles(); + + let _ = bundles + .format_value_sync("missing-message", None, &mut errors) + .unwrap(); + assert_eq!( + errors, + vec![ + LocalizationError::MissingMessage { + id: "missing-message".to_string(), + locale: Some(langid!("pl")) + }, + LocalizationError::MissingMessage { + id: "missing-message".to_string(), + locale: Some(langid!("en-US")) + }, + LocalizationError::MissingMessage { + id: "missing-message".to_string(), + locale: None + }, + ] + ); + + errors.clear(); + + let _ = bundles + .format_value_sync("message-3", None, &mut errors) + .unwrap(); + assert_eq!( + errors, + vec![ + LocalizationError::MissingValue { + id: "message-3".to_string(), + locale: Some(langid!("pl")) + }, + LocalizationError::MissingValue { + id: "message-3".to_string(), + locale: Some(langid!("en-US")) + }, + LocalizationError::MissingValue { + id: "message-3".to_string(), + locale: None + }, + ] + ); +} + +#[test] +fn localization_format_values_sync_missing_errors() { + let resource_ids: Vec = vec!["test.ftl".into(), "test2.ftl".into()]; + + let locales = Locales::new(vec![langid!("pl"), langid!("en-US")]); + let res_mgr = ResourceManager; + let mut errors = vec![]; + + let loc = Localization::with_env(resource_ids, true, locales.clone(), res_mgr); + let bundles = loc.bundles(); + + let _ = bundles + .format_values_sync( + &["missing-message".into(), "missing-message-2".into()], + &mut errors, + ) + .unwrap(); + assert_eq!( + errors, + vec![ + LocalizationError::MissingMessage { + id: "missing-message".to_string(), + locale: Some(langid!("pl")) + }, + LocalizationError::MissingMessage { + id: "missing-message-2".to_string(), + locale: Some(langid!("pl")) + }, + LocalizationError::MissingMessage { + id: "missing-message".to_string(), + locale: Some(langid!("en-US")) + }, + LocalizationError::MissingMessage { + id: "missing-message-2".to_string(), + locale: Some(langid!("en-US")) + }, + LocalizationError::MissingMessage { + id: "missing-message".to_string(), + locale: None + }, + LocalizationError::MissingMessage { + id: "missing-message-2".to_string(), + locale: None + }, + ] + ); + + errors.clear(); + + let _ = bundles + .format_values_sync(&["message-3".into()], &mut errors) + .unwrap(); + assert_eq!( + errors, + vec![ + LocalizationError::MissingValue { + id: "message-3".to_string(), + locale: Some(langid!("pl")) + }, + LocalizationError::MissingValue { + id: "message-3".to_string(), + locale: Some(langid!("en-US")) + }, + LocalizationError::MissingValue { + id: "message-3".to_string(), + locale: None + }, + ] + ); +} + +#[test] +fn localization_format_messages_sync_missing_errors() { + let resource_ids: Vec = vec!["test.ftl".into(), "test2.ftl".into()]; + + let locales = Locales::new(vec![langid!("pl"), langid!("en-US")]); + let res_mgr = ResourceManager; + let mut errors = vec![]; + + let loc = Localization::with_env(resource_ids, true, locales.clone(), res_mgr); + let bundles = loc.bundles(); + + let _ = bundles + .format_messages_sync( + &["missing-message".into(), "missing-message-2".into()], + &mut errors, + ) + .unwrap(); + assert_eq!( + errors, + vec![ + LocalizationError::MissingMessage { + id: "missing-message".to_string(), + locale: Some(langid!("pl")) + }, + LocalizationError::MissingMessage { + id: "missing-message-2".to_string(), + locale: Some(langid!("pl")) + }, + LocalizationError::MissingMessage { + id: "missing-message".to_string(), + locale: Some(langid!("en-US")) + }, + LocalizationError::MissingMessage { + id: "missing-message-2".to_string(), + locale: Some(langid!("en-US")) + }, + LocalizationError::MissingMessage { + id: "missing-message".to_string(), + locale: None + }, + LocalizationError::MissingMessage { + id: "missing-message-2".to_string(), + locale: None + }, + ] + ); +} + +#[test] +fn localization_format_missing_argument_error() { + let resource_ids: Vec = vec!["test2.ftl".into()]; + let locales = Locales::new(vec![langid!("en-US")]); + let res_mgr = ResourceManager; + let mut errors = vec![]; + + let loc = Localization::with_env(resource_ids, true, locales, res_mgr); + let bundles = loc.bundles(); + + let mut args = FluentArgs::new(); + args.set("userName", "John"); + let keys = vec![L10nKey { + id: "message-4".into(), + args: Some(args), + }]; + + let msgs = bundles.format_messages_sync(&keys, &mut errors).unwrap(); + assert_eq!( + msgs.get(0).unwrap().as_ref().unwrap().value, + Some(Cow::Borrowed("Hello, John. [en]")) + ); + assert_eq!(errors.len(), 0); + + let keys = vec![L10nKey { + id: "message-4".into(), + args: None, + }]; + let msgs = bundles.format_messages_sync(&keys, &mut errors).unwrap(); + assert_eq!( + msgs.get(0).unwrap().as_ref().unwrap().value, + Some(Cow::Borrowed("Hello, {$userName}. [en]")) + ); + assert_eq!( + errors, + vec![LocalizationError::Resolver { + id: "message-4".to_string(), + locale: langid!("en-US"), + errors: vec![FluentError::ResolverError(ResolverError::Reference( + ReferenceKind::Variable { + id: "userName".to_string(), + } + ))], + },] + ); +} + +#[tokio::test] +async fn localization_handle_state_changes_mid_async() { + let resource_ids: Vec = vec!["test.ftl".into()]; + let locales = Locales::new(vec![langid!("en-US")]); + let res_mgr = ResourceManager; + let mut errors = vec![]; + + let mut loc = Localization::with_env(resource_ids, false, locales, res_mgr); + + let bundles = loc.bundles().clone(); + + loc.add_resource_id("test2.ftl".to_string()); + + bundles.format_value("key", None, &mut errors).await; +} diff --git a/third_party/rust/fluent-fallback/tests/resources/en-US/test.ftl b/third_party/rust/fluent-fallback/tests/resources/en-US/test.ftl new file mode 100644 index 000000000000..c6a7390acf2f --- /dev/null +++ b/third_party/rust/fluent-fallback/tests/resources/en-US/test.ftl @@ -0,0 +1,4 @@ +hello-world = Hello World [en] + +message-1 = Message 1 Value [en] + .attr1 = Message 1 Attribute [en] diff --git a/third_party/rust/fluent-fallback/tests/resources/en-US/test2.ftl b/third_party/rust/fluent-fallback/tests/resources/en-US/test2.ftl new file mode 100644 index 000000000000..faeae76cfeed --- /dev/null +++ b/third_party/rust/fluent-fallback/tests/resources/en-US/test2.ftl @@ -0,0 +1,10 @@ +hello-world-2 = Hello World 2 [en] +hello-world-3 = Hello World 3 [en] + +message-2 = Message 2 Value [en] + .attr1 = Message 2 Attribute [en] + +message-3 = + .attr1 = Message 3 Attribute [en] + +message-4 = Hello, { $userName }. [en] diff --git a/third_party/rust/fluent-fallback/tests/resources/pl/test.ftl b/third_party/rust/fluent-fallback/tests/resources/pl/test.ftl new file mode 100644 index 000000000000..6e2f8835f146 --- /dev/null +++ b/third_party/rust/fluent-fallback/tests/resources/pl/test.ftl @@ -0,0 +1,4 @@ +hello-world = Hello World [pl] + +message-1 = Message 1 Value [pl] + .attr1 = Message 1 Attribute [pl] diff --git a/third_party/rust/fluent-fallback/tests/resources/pl/test2.ftl b/third_party/rust/fluent-fallback/tests/resources/pl/test2.ftl new file mode 100644 index 000000000000..35d646a5202e --- /dev/null +++ b/third_party/rust/fluent-fallback/tests/resources/pl/test2.ftl @@ -0,0 +1,9 @@ +hello-world-2 = Hello World 2 [pl] + +message-2 = Message 2 Value [pl] + .attr1 = Message 2 Attribute [pl] + +message-3 = + .attr1 = Message 3 Attribute [pl] + +message-4 = Hello, { $userName }. [pl] diff --git a/third_party/rust/futures-executor/.cargo-checksum.json b/third_party/rust/futures-executor/.cargo-checksum.json new file mode 100644 index 000000000000..eca63238e94a --- /dev/null +++ b/third_party/rust/futures-executor/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"e6cacb38174963e31cebfcee26619efa118915bac81086756fd474d563101d68","LICENSE-APACHE":"275c491d6d1160553c32fd6127061d7f9606c3ea25abfad6ca3f6ed088785427","LICENSE-MIT":"6652c868f35dfe5e8ef636810a4e576b9d663f3a17fb0f5613ad73583e1b88fd","benches/thread_notify.rs":"e601968527bee85766f32d2d11de5ed8f6b4bd5a29989b5c369a52bd3cd3d024","src/enter.rs":"c1a771f373b469d98e2599d8e37da7d7a7083c30332d643f37867f86406ab1e2","src/lib.rs":"0ceeed35d70e3d890ad71d1ac691d372dca3a6e180949b50b261aecbfbd9f697","src/local_pool.rs":"1661a58468491d714a358b6382df88bbd7557e19506009763f841cbcf85781f5","src/thread_pool.rs":"9667ce8e99fb6a08675a238aea6c33a4b2c9a94f6a42459e56a6b8cee84b6804","src/unpark_mutex.rs":"e186464d9bdec22a6d1e1d900ed03a1154e6b0d422ede9bd3b768657cdbb6113","tests/local_pool.rs":"d12d2f6240ec6ab1b2e189cd8aac9548249ca66ccdb5f3b142e237b320f812c4"},"package":"badaa6a909fac9e7236d0620a2f57f7664640c56575b71a7552fbd68deafab79"} \ No newline at end of file diff --git a/third_party/rust/futures-executor/Cargo.toml b/third_party/rust/futures-executor/Cargo.toml new file mode 100644 index 000000000000..17383a431b1b --- /dev/null +++ b/third_party/rust/futures-executor/Cargo.toml @@ -0,0 +1,47 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies +# +# If you believe there's an error in this file please file an +# issue against the rust-lang/cargo repository. If you're +# editing this file be aware that the upstream Cargo.toml +# will likely look very different (and much more reasonable) + +[package] +edition = "2018" +name = "futures-executor" +version = "0.3.15" +authors = ["Alex Crichton "] +description = "Executors for asynchronous tasks based on the futures-rs library.\n" +homepage = "https://rust-lang.github.io/futures-rs" +documentation = "https://docs.rs/futures-executor/0.3" +license = "MIT OR Apache-2.0" +repository = "https://github.com/rust-lang/futures-rs" +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] +[dependencies.futures-core] +version = "0.3.15" +default-features = false + +[dependencies.futures-task] +version = "0.3.15" +default-features = false + +[dependencies.futures-util] +version = "0.3.15" +default-features = false + +[dependencies.num_cpus] +version = "1.8.0" +optional = true + +[dev-dependencies] + +[features] +default = ["std"] +std = ["futures-core/std", "futures-task/std", "futures-util/std"] +thread-pool = ["std", "num_cpus"] diff --git a/third_party/rust/futures-executor/LICENSE-APACHE b/third_party/rust/futures-executor/LICENSE-APACHE new file mode 100644 index 000000000000..9eb0b097f5d0 --- /dev/null +++ b/third_party/rust/futures-executor/LICENSE-APACHE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright (c) 2016 Alex Crichton +Copyright (c) 2017 The Tokio Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/third_party/rust/futures-executor/LICENSE-MIT b/third_party/rust/futures-executor/LICENSE-MIT new file mode 100644 index 000000000000..8ad082ec4f93 --- /dev/null +++ b/third_party/rust/futures-executor/LICENSE-MIT @@ -0,0 +1,26 @@ +Copyright (c) 2016 Alex Crichton +Copyright (c) 2017 The Tokio Authors + +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/third_party/rust/futures-executor/benches/thread_notify.rs b/third_party/rust/futures-executor/benches/thread_notify.rs new file mode 100644 index 000000000000..88d0447cf68b --- /dev/null +++ b/third_party/rust/futures-executor/benches/thread_notify.rs @@ -0,0 +1,109 @@ +#![feature(test)] + +extern crate test; +use crate::test::Bencher; + +use futures::executor::block_on; +use futures::future::Future; +use futures::task::{Context, Poll, Waker}; +use std::pin::Pin; + +#[bench] +fn thread_yield_single_thread_one_wait(b: &mut Bencher) { + const NUM: usize = 10_000; + + struct Yield { + rem: usize, + } + + impl Future for Yield { + type Output = (); + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if self.rem == 0 { + Poll::Ready(()) + } else { + self.rem -= 1; + cx.waker().wake_by_ref(); + Poll::Pending + } + } + } + + b.iter(|| { + let y = Yield { rem: NUM }; + block_on(y); + }); +} + +#[bench] +fn thread_yield_single_thread_many_wait(b: &mut Bencher) { + const NUM: usize = 10_000; + + struct Yield { + rem: usize, + } + + impl Future for Yield { + type Output = (); + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if self.rem == 0 { + Poll::Ready(()) + } else { + self.rem -= 1; + cx.waker().wake_by_ref(); + Poll::Pending + } + } + } + + b.iter(|| { + for _ in 0..NUM { + let y = Yield { rem: 1 }; + block_on(y); + } + }); +} + +#[bench] +fn thread_yield_multi_thread(b: &mut Bencher) { + use std::sync::mpsc; + use std::thread; + + const NUM: usize = 1_000; + + let (tx, rx) = mpsc::sync_channel::(10_000); + + struct Yield { + rem: usize, + tx: mpsc::SyncSender, + } + impl Unpin for Yield {} + + impl Future for Yield { + type Output = (); + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if self.rem == 0 { + Poll::Ready(()) + } else { + self.rem -= 1; + self.tx.send(cx.waker().clone()).unwrap(); + Poll::Pending + } + } + } + + thread::spawn(move || { + while let Ok(task) = rx.recv() { + task.wake(); + } + }); + + b.iter(move || { + let y = Yield { rem: NUM, tx: tx.clone() }; + + block_on(y); + }); +} diff --git a/third_party/rust/futures-executor/src/enter.rs b/third_party/rust/futures-executor/src/enter.rs new file mode 100644 index 000000000000..5895a9efb640 --- /dev/null +++ b/third_party/rust/futures-executor/src/enter.rs @@ -0,0 +1,80 @@ +use std::cell::Cell; +use std::fmt; + +thread_local!(static ENTERED: Cell = Cell::new(false)); + +/// Represents an executor context. +/// +/// For more details, see [`enter` documentation](enter()). +pub struct Enter { + _priv: (), +} + +/// An error returned by `enter` if an execution scope has already been +/// entered. +pub struct EnterError { + _priv: (), +} + +impl fmt::Debug for EnterError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("EnterError").finish() + } +} + +impl fmt::Display for EnterError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "an execution scope has already been entered") + } +} + +impl std::error::Error for EnterError {} + +/// Marks the current thread as being within the dynamic extent of an +/// executor. +/// +/// Executor implementations should call this function before beginning to +/// execute a tasks, and drop the returned [`Enter`](Enter) value after +/// completing task execution: +/// +/// ``` +/// use futures::executor::enter; +/// +/// let enter = enter().expect("..."); +/// /* run task */ +/// drop(enter); +/// ``` +/// +/// Doing so ensures that executors aren't +/// accidentally invoked in a nested fashion. +/// +/// # Error +/// +/// Returns an error if the current thread is already marked, in which case the +/// caller should panic with a tailored error message. +pub fn enter() -> Result { + ENTERED.with(|c| { + if c.get() { + Err(EnterError { _priv: () }) + } else { + c.set(true); + + Ok(Enter { _priv: () }) + } + }) +} + +impl fmt::Debug for Enter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Enter").finish() + } +} + +impl Drop for Enter { + fn drop(&mut self) { + ENTERED.with(|c| { + assert!(c.get()); + c.set(false); + }); + } +} diff --git a/third_party/rust/futures-executor/src/lib.rs b/third_party/rust/futures-executor/src/lib.rs new file mode 100644 index 000000000000..873ac7a6846d --- /dev/null +++ b/third_party/rust/futures-executor/src/lib.rs @@ -0,0 +1,67 @@ +//! Built-in executors and related tools. +//! +//! All asynchronous computation occurs within an executor, which is +//! capable of spawning futures as tasks. This module provides several +//! built-in executors, as well as tools for building your own. +//! +//! All items are only available when the `std` feature of this +//! library is activated, and it is activated by default. +//! +//! # Using a thread pool (M:N task scheduling) +//! +//! Most of the time tasks should be executed on a [thread pool](ThreadPool). +//! A small set of worker threads can handle a very large set of spawned tasks +//! (which are much lighter weight than threads). Tasks spawned onto the pool +//! with the [`spawn_ok`](ThreadPool::spawn_ok) function will run ambiently on +//! the created threads. +//! +//! # Spawning additional tasks +//! +//! Tasks can be spawned onto a spawner by calling its [`spawn_obj`] method +//! directly. In the case of `!Send` futures, [`spawn_local_obj`] can be used +//! instead. +//! +//! # Single-threaded execution +//! +//! In addition to thread pools, it's possible to run a task (and the tasks +//! it spawns) entirely within a single thread via the [`LocalPool`] executor. +//! Aside from cutting down on synchronization costs, this executor also makes +//! it possible to spawn non-`Send` tasks, via [`spawn_local_obj`]. The +//! [`LocalPool`] is best suited for running I/O-bound tasks that do relatively +//! little work between I/O operations. +//! +//! There is also a convenience function [`block_on`] for simply running a +//! future to completion on the current thread. +//! +//! [`spawn_obj`]: https://docs.rs/futures/0.3/futures/task/trait.Spawn.html#tymethod.spawn_obj +//! [`spawn_local_obj`]: https://docs.rs/futures/0.3/futures/task/trait.LocalSpawn.html#tymethod.spawn_local_obj + +#![cfg_attr(not(feature = "std"), no_std)] +#![warn(missing_docs, missing_debug_implementations, rust_2018_idioms, unreachable_pub)] +// It cannot be included in the published code because this lints have false positives in the minimum required version. +#![cfg_attr(test, warn(single_use_lifetimes))] +#![warn(clippy::all)] +#![doc(test(attr(deny(warnings), allow(dead_code, unused_assignments, unused_variables))))] +#![cfg_attr(docsrs, feature(doc_cfg))] + +#[cfg(feature = "std")] +mod local_pool; +#[cfg(feature = "std")] +pub use crate::local_pool::{block_on, block_on_stream, BlockingStream, LocalPool, LocalSpawner}; + +#[cfg(feature = "thread-pool")] +#[cfg_attr(docsrs, doc(cfg(feature = "thread-pool")))] +#[cfg(feature = "std")] +mod thread_pool; +#[cfg(feature = "thread-pool")] +#[cfg(feature = "std")] +mod unpark_mutex; +#[cfg(feature = "thread-pool")] +#[cfg_attr(docsrs, doc(cfg(feature = "thread-pool")))] +#[cfg(feature = "std")] +pub use crate::thread_pool::{ThreadPool, ThreadPoolBuilder}; + +#[cfg(feature = "std")] +mod enter; +#[cfg(feature = "std")] +pub use crate::enter::{enter, Enter, EnterError}; diff --git a/third_party/rust/futures-executor/src/local_pool.rs b/third_party/rust/futures-executor/src/local_pool.rs new file mode 100644 index 000000000000..bee96d8db9f9 --- /dev/null +++ b/third_party/rust/futures-executor/src/local_pool.rs @@ -0,0 +1,400 @@ +use crate::enter; +use futures_core::future::Future; +use futures_core::stream::Stream; +use futures_core::task::{Context, Poll}; +use futures_task::{waker_ref, ArcWake}; +use futures_task::{FutureObj, LocalFutureObj, LocalSpawn, Spawn, SpawnError}; +use futures_util::pin_mut; +use futures_util::stream::FuturesUnordered; +use futures_util::stream::StreamExt; +use std::cell::RefCell; +use std::ops::{Deref, DerefMut}; +use std::rc::{Rc, Weak}; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, +}; +use std::thread::{self, Thread}; + +/// A single-threaded task pool for polling futures to completion. +/// +/// This executor allows you to multiplex any number of tasks onto a single +/// thread. It's appropriate to poll strictly I/O-bound futures that do very +/// little work in between I/O actions. +/// +/// To get a handle to the pool that implements +/// [`Spawn`](futures_task::Spawn), use the +/// [`spawner()`](LocalPool::spawner) method. Because the executor is +/// single-threaded, it supports a special form of task spawning for non-`Send` +/// futures, via [`spawn_local_obj`](futures_task::LocalSpawn::spawn_local_obj). +#[derive(Debug)] +pub struct LocalPool { + pool: FuturesUnordered>, + incoming: Rc, +} + +/// A handle to a [`LocalPool`](LocalPool) that implements +/// [`Spawn`](futures_task::Spawn). +#[derive(Clone, Debug)] +pub struct LocalSpawner { + incoming: Weak, +} + +type Incoming = RefCell>>; + +pub(crate) struct ThreadNotify { + /// The (single) executor thread. + thread: Thread, + /// A flag to ensure a wakeup (i.e. `unpark()`) is not "forgotten" + /// before the next `park()`, which may otherwise happen if the code + /// being executed as part of the future(s) being polled makes use of + /// park / unpark calls of its own, i.e. we cannot assume that no other + /// code uses park / unpark on the executing `thread`. + unparked: AtomicBool, +} + +thread_local! { + static CURRENT_THREAD_NOTIFY: Arc = Arc::new(ThreadNotify { + thread: thread::current(), + unparked: AtomicBool::new(false), + }); +} + +impl ArcWake for ThreadNotify { + fn wake_by_ref(arc_self: &Arc) { + // Make sure the wakeup is remembered until the next `park()`. + let unparked = arc_self.unparked.swap(true, Ordering::Relaxed); + if !unparked { + // If the thread has not been unparked yet, it must be done + // now. If it was actually parked, it will run again, + // otherwise the token made available by `unpark` + // may be consumed before reaching `park()`, but `unparked` + // ensures it is not forgotten. + arc_self.thread.unpark(); + } + } +} + +// Set up and run a basic single-threaded spawner loop, invoking `f` on each +// turn. +fn run_executor) -> Poll>(mut f: F) -> T { + let _enter = enter().expect( + "cannot execute `LocalPool` executor from within \ + another executor", + ); + + CURRENT_THREAD_NOTIFY.with(|thread_notify| { + let waker = waker_ref(thread_notify); + let mut cx = Context::from_waker(&waker); + loop { + if let Poll::Ready(t) = f(&mut cx) { + return t; + } + // Consume the wakeup that occurred while executing `f`, if any. + let unparked = thread_notify.unparked.swap(false, Ordering::Acquire); + if !unparked { + // No wakeup occurred. It may occur now, right before parking, + // but in that case the token made available by `unpark()` + // is guaranteed to still be available and `park()` is a no-op. + thread::park(); + // When the thread is unparked, `unparked` will have been set + // and needs to be unset before the next call to `f` to avoid + // a redundant loop iteration. + thread_notify.unparked.store(false, Ordering::Release); + } + } + }) +} + +fn poll_executor) -> T>(mut f: F) -> T { + let _enter = enter().expect( + "cannot execute `LocalPool` executor from within \ + another executor", + ); + + CURRENT_THREAD_NOTIFY.with(|thread_notify| { + let waker = waker_ref(thread_notify); + let mut cx = Context::from_waker(&waker); + f(&mut cx) + }) +} + +impl LocalPool { + /// Create a new, empty pool of tasks. + pub fn new() -> Self { + Self { pool: FuturesUnordered::new(), incoming: Default::default() } + } + + /// Get a clonable handle to the pool as a [`Spawn`]. + pub fn spawner(&self) -> LocalSpawner { + LocalSpawner { incoming: Rc::downgrade(&self.incoming) } + } + + /// Run all tasks in the pool to completion. + /// + /// ``` + /// use futures::executor::LocalPool; + /// + /// let mut pool = LocalPool::new(); + /// + /// // ... spawn some initial tasks using `spawn.spawn()` or `spawn.spawn_local()` + /// + /// // run *all* tasks in the pool to completion, including any newly-spawned ones. + /// pool.run(); + /// ``` + /// + /// The function will block the calling thread until *all* tasks in the pool + /// are complete, including any spawned while running existing tasks. + pub fn run(&mut self) { + run_executor(|cx| self.poll_pool(cx)) + } + + /// Runs all the tasks in the pool until the given future completes. + /// + /// ``` + /// use futures::executor::LocalPool; + /// + /// let mut pool = LocalPool::new(); + /// # let my_app = async {}; + /// + /// // run tasks in the pool until `my_app` completes + /// pool.run_until(my_app); + /// ``` + /// + /// The function will block the calling thread *only* until the future `f` + /// completes; there may still be incomplete tasks in the pool, which will + /// be inert after the call completes, but can continue with further use of + /// one of the pool's run or poll methods. While the function is running, + /// however, all tasks in the pool will try to make progress. + pub fn run_until(&mut self, future: F) -> F::Output { + pin_mut!(future); + + run_executor(|cx| { + { + // if our main task is done, so are we + let result = future.as_mut().poll(cx); + if let Poll::Ready(output) = result { + return Poll::Ready(output); + } + } + + let _ = self.poll_pool(cx); + Poll::Pending + }) + } + + /// Runs all tasks and returns after completing one future or until no more progress + /// can be made. Returns `true` if one future was completed, `false` otherwise. + /// + /// ``` + /// use futures::executor::LocalPool; + /// use futures::task::LocalSpawnExt; + /// use futures::future::{ready, pending}; + /// + /// let mut pool = LocalPool::new(); + /// let spawner = pool.spawner(); + /// + /// spawner.spawn_local(ready(())).unwrap(); + /// spawner.spawn_local(ready(())).unwrap(); + /// spawner.spawn_local(pending()).unwrap(); + /// + /// // Run the two ready tasks and return true for them. + /// pool.try_run_one(); // returns true after completing one of the ready futures + /// pool.try_run_one(); // returns true after completing the other ready future + /// + /// // the remaining task can not be completed + /// assert!(!pool.try_run_one()); // returns false + /// ``` + /// + /// This function will not block the calling thread and will return the moment + /// that there are no tasks left for which progress can be made or after exactly one + /// task was completed; Remaining incomplete tasks in the pool can continue with + /// further use of one of the pool's run or poll methods. + /// Though only one task will be completed, progress may be made on multiple tasks. + pub fn try_run_one(&mut self) -> bool { + poll_executor(|ctx| { + loop { + let ret = self.poll_pool_once(ctx); + + // return if we have executed a future + if let Poll::Ready(Some(_)) = ret { + return true; + } + + // if there are no new incoming futures + // then there is no feature that can make progress + // and we can return without having completed a single future + if self.incoming.borrow().is_empty() { + return false; + } + } + }) + } + + /// Runs all tasks in the pool and returns if no more progress can be made + /// on any task. + /// + /// ``` + /// use futures::executor::LocalPool; + /// use futures::task::LocalSpawnExt; + /// use futures::future::{ready, pending}; + /// + /// let mut pool = LocalPool::new(); + /// let spawner = pool.spawner(); + /// + /// spawner.spawn_local(ready(())).unwrap(); + /// spawner.spawn_local(ready(())).unwrap(); + /// spawner.spawn_local(pending()).unwrap(); + /// + /// // Runs the two ready task and returns. + /// // The empty task remains in the pool. + /// pool.run_until_stalled(); + /// ``` + /// + /// This function will not block the calling thread and will return the moment + /// that there are no tasks left for which progress can be made; + /// remaining incomplete tasks in the pool can continue with further use of one + /// of the pool's run or poll methods. While the function is running, all tasks + /// in the pool will try to make progress. + pub fn run_until_stalled(&mut self) { + poll_executor(|ctx| { + let _ = self.poll_pool(ctx); + }); + } + + // Make maximal progress on the entire pool of spawned task, returning `Ready` + // if the pool is empty and `Pending` if no further progress can be made. + fn poll_pool(&mut self, cx: &mut Context<'_>) -> Poll<()> { + // state for the FuturesUnordered, which will never be used + loop { + let ret = self.poll_pool_once(cx); + + // we queued up some new tasks; add them and poll again + if !self.incoming.borrow().is_empty() { + continue; + } + + // no queued tasks; we may be done + match ret { + Poll::Pending => return Poll::Pending, + Poll::Ready(None) => return Poll::Ready(()), + _ => {} + } + } + } + + // Try make minimal progress on the pool of spawned tasks + fn poll_pool_once(&mut self, cx: &mut Context<'_>) -> Poll> { + // empty the incoming queue of newly-spawned tasks + { + let mut incoming = self.incoming.borrow_mut(); + for task in incoming.drain(..) { + self.pool.push(task) + } + } + + // try to execute the next ready future + self.pool.poll_next_unpin(cx) + } +} + +impl Default for LocalPool { + fn default() -> Self { + Self::new() + } +} + +/// Run a future to completion on the current thread. +/// +/// This function will block the caller until the given future has completed. +/// +/// Use a [`LocalPool`](LocalPool) if you need finer-grained control over +/// spawned tasks. +pub fn block_on(f: F) -> F::Output { + pin_mut!(f); + run_executor(|cx| f.as_mut().poll(cx)) +} + +/// Turn a stream into a blocking iterator. +/// +/// When `next` is called on the resulting `BlockingStream`, the caller +/// will be blocked until the next element of the `Stream` becomes available. +pub fn block_on_stream(stream: S) -> BlockingStream { + BlockingStream { stream } +} + +/// An iterator which blocks on values from a stream until they become available. +#[derive(Debug)] +pub struct BlockingStream { + stream: S, +} + +impl Deref for BlockingStream { + type Target = S; + fn deref(&self) -> &Self::Target { + &self.stream + } +} + +impl DerefMut for BlockingStream { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.stream + } +} + +impl BlockingStream { + /// Convert this `BlockingStream` into the inner `Stream` type. + pub fn into_inner(self) -> S { + self.stream + } +} + +impl Iterator for BlockingStream { + type Item = S::Item; + + fn next(&mut self) -> Option { + LocalPool::new().run_until(self.stream.next()) + } + + fn size_hint(&self) -> (usize, Option) { + self.stream.size_hint() + } +} + +impl Spawn for LocalSpawner { + fn spawn_obj(&self, future: FutureObj<'static, ()>) -> Result<(), SpawnError> { + if let Some(incoming) = self.incoming.upgrade() { + incoming.borrow_mut().push(future.into()); + Ok(()) + } else { + Err(SpawnError::shutdown()) + } + } + + fn status(&self) -> Result<(), SpawnError> { + if self.incoming.upgrade().is_some() { + Ok(()) + } else { + Err(SpawnError::shutdown()) + } + } +} + +impl LocalSpawn for LocalSpawner { + fn spawn_local_obj(&self, future: LocalFutureObj<'static, ()>) -> Result<(), SpawnError> { + if let Some(incoming) = self.incoming.upgrade() { + incoming.borrow_mut().push(future); + Ok(()) + } else { + Err(SpawnError::shutdown()) + } + } + + fn status_local(&self) -> Result<(), SpawnError> { + if self.incoming.upgrade().is_some() { + Ok(()) + } else { + Err(SpawnError::shutdown()) + } + } +} diff --git a/third_party/rust/futures-executor/src/thread_pool.rs b/third_party/rust/futures-executor/src/thread_pool.rs new file mode 100644 index 000000000000..f2347dbbdfcf --- /dev/null +++ b/third_party/rust/futures-executor/src/thread_pool.rs @@ -0,0 +1,375 @@ +use crate::enter; +use crate::unpark_mutex::UnparkMutex; +use futures_core::future::Future; +use futures_core::task::{Context, Poll}; +use futures_task::{waker_ref, ArcWake}; +use futures_task::{FutureObj, Spawn, SpawnError}; +use futures_util::future::FutureExt; +use std::cmp; +use std::fmt; +use std::io; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::mpsc; +use std::sync::{Arc, Mutex}; +use std::thread; + +/// A general-purpose thread pool for scheduling tasks that poll futures to +/// completion. +/// +/// The thread pool multiplexes any number of tasks onto a fixed number of +/// worker threads. +/// +/// This type is a clonable handle to the threadpool itself. +/// Cloning it will only create a new reference, not a new threadpool. +/// +/// This type is only available when the `thread-pool` feature of this +/// library is activated. +#[cfg_attr(docsrs, doc(cfg(feature = "thread-pool")))] +pub struct ThreadPool { + state: Arc, +} + +/// Thread pool configuration object. +/// +/// This type is only available when the `thread-pool` feature of this +/// library is activated. +#[cfg_attr(docsrs, doc(cfg(feature = "thread-pool")))] +pub struct ThreadPoolBuilder { + pool_size: usize, + stack_size: usize, + name_prefix: Option, + after_start: Option>, + before_stop: Option>, +} + +trait AssertSendSync: Send + Sync {} +impl AssertSendSync for ThreadPool {} + +struct PoolState { + tx: Mutex>, + rx: Mutex>, + cnt: AtomicUsize, + size: usize, +} + +impl fmt::Debug for ThreadPool { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ThreadPool").field("size", &self.state.size).finish() + } +} + +impl fmt::Debug for ThreadPoolBuilder { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ThreadPoolBuilder") + .field("pool_size", &self.pool_size) + .field("name_prefix", &self.name_prefix) + .finish() + } +} + +enum Message { + Run(Task), + Close, +} + +impl ThreadPool { + /// Creates a new thread pool with the default configuration. + /// + /// See documentation for the methods in + /// [`ThreadPoolBuilder`](ThreadPoolBuilder) for details on the default + /// configuration. + pub fn new() -> Result { + ThreadPoolBuilder::new().create() + } + + /// Create a default thread pool configuration, which can then be customized. + /// + /// See documentation for the methods in + /// [`ThreadPoolBuilder`](ThreadPoolBuilder) for details on the default + /// configuration. + pub fn builder() -> ThreadPoolBuilder { + ThreadPoolBuilder::new() + } + + /// Spawns a future that will be run to completion. + /// + /// > **Note**: This method is similar to `Spawn::spawn_obj`, except that + /// > it is guaranteed to always succeed. + pub fn spawn_obj_ok(&self, future: FutureObj<'static, ()>) { + let task = Task { + future, + wake_handle: Arc::new(WakeHandle { exec: self.clone(), mutex: UnparkMutex::new() }), + exec: self.clone(), + }; + self.state.send(Message::Run(task)); + } + + /// Spawns a task that polls the given future with output `()` to + /// completion. + /// + /// ``` + /// use futures::executor::ThreadPool; + /// + /// let pool = ThreadPool::new().unwrap(); + /// + /// let future = async { /* ... */ }; + /// pool.spawn_ok(future); + /// ``` + /// + /// > **Note**: This method is similar to `SpawnExt::spawn`, except that + /// > it is guaranteed to always succeed. + pub fn spawn_ok(&self, future: Fut) + where + Fut: Future + Send + 'static, + { + self.spawn_obj_ok(FutureObj::new(Box::new(future))) + } +} + +impl Spawn for ThreadPool { + fn spawn_obj(&self, future: FutureObj<'static, ()>) -> Result<(), SpawnError> { + self.spawn_obj_ok(future); + Ok(()) + } +} + +impl PoolState { + fn send(&self, msg: Message) { + self.tx.lock().unwrap().send(msg).unwrap(); + } + + fn work( + &self, + idx: usize, + after_start: Option>, + before_stop: Option>, + ) { + let _scope = enter().unwrap(); + if let Some(after_start) = after_start { + after_start(idx); + } + loop { + let msg = self.rx.lock().unwrap().recv().unwrap(); + match msg { + Message::Run(task) => task.run(), + Message::Close => break, + } + } + if let Some(before_stop) = before_stop { + before_stop(idx); + } + } +} + +impl Clone for ThreadPool { + fn clone(&self) -> Self { + self.state.cnt.fetch_add(1, Ordering::Relaxed); + Self { state: self.state.clone() } + } +} + +impl Drop for ThreadPool { + fn drop(&mut self) { + if self.state.cnt.fetch_sub(1, Ordering::Relaxed) == 1 { + for _ in 0..self.state.size { + self.state.send(Message::Close); + } + } + } +} + +impl ThreadPoolBuilder { + /// Create a default thread pool configuration. + /// + /// See the other methods on this type for details on the defaults. + pub fn new() -> Self { + Self { + pool_size: cmp::max(1, num_cpus::get()), + stack_size: 0, + name_prefix: None, + after_start: None, + before_stop: None, + } + } + + /// Set size of a future ThreadPool + /// + /// The size of a thread pool is the number of worker threads spawned. By + /// default, this is equal to the number of CPU cores. + /// + /// # Panics + /// + /// Panics if `pool_size == 0`. + pub fn pool_size(&mut self, size: usize) -> &mut Self { + assert!(size > 0); + self.pool_size = size; + self + } + + /// Set stack size of threads in the pool, in bytes. + /// + /// By default, worker threads use Rust's standard stack size. + pub fn stack_size(&mut self, stack_size: usize) -> &mut Self { + self.stack_size = stack_size; + self + } + + /// Set thread name prefix of a future ThreadPool. + /// + /// Thread name prefix is used for generating thread names. For example, if prefix is + /// `my-pool-`, then threads in the pool will get names like `my-pool-1` etc. + /// + /// By default, worker threads are assigned Rust's standard thread name. + pub fn name_prefix>(&mut self, name_prefix: S) -> &mut Self { + self.name_prefix = Some(name_prefix.into()); + self + } + + /// Execute the closure `f` immediately after each worker thread is started, + /// but before running any tasks on it. + /// + /// This hook is intended for bookkeeping and monitoring. + /// The closure `f` will be dropped after the `builder` is dropped + /// and all worker threads in the pool have executed it. + /// + /// The closure provided will receive an index corresponding to the worker + /// thread it's running on. + pub fn after_start(&mut self, f: F) -> &mut Self + where + F: Fn(usize) + Send + Sync + 'static, + { + self.after_start = Some(Arc::new(f)); + self + } + + /// Execute closure `f` just prior to shutting down each worker thread. + /// + /// This hook is intended for bookkeeping and monitoring. + /// The closure `f` will be dropped after the `builder` is droppped + /// and all threads in the pool have executed it. + /// + /// The closure provided will receive an index corresponding to the worker + /// thread it's running on. + pub fn before_stop(&mut self, f: F) -> &mut Self + where + F: Fn(usize) + Send + Sync + 'static, + { + self.before_stop = Some(Arc::new(f)); + self + } + + /// Create a [`ThreadPool`](ThreadPool) with the given configuration. + pub fn create(&mut self) -> Result { + let (tx, rx) = mpsc::channel(); + let pool = ThreadPool { + state: Arc::new(PoolState { + tx: Mutex::new(tx), + rx: Mutex::new(rx), + cnt: AtomicUsize::new(1), + size: self.pool_size, + }), + }; + + for counter in 0..self.pool_size { + let state = pool.state.clone(); + let after_start = self.after_start.clone(); + let before_stop = self.before_stop.clone(); + let mut thread_builder = thread::Builder::new(); + if let Some(ref name_prefix) = self.name_prefix { + thread_builder = thread_builder.name(format!("{}{}", name_prefix, counter)); + } + if self.stack_size > 0 { + thread_builder = thread_builder.stack_size(self.stack_size); + } + thread_builder.spawn(move || state.work(counter, after_start, before_stop))?; + } + Ok(pool) + } +} + +impl Default for ThreadPoolBuilder { + fn default() -> Self { + Self::new() + } +} + +/// A task responsible for polling a future to completion. +struct Task { + future: FutureObj<'static, ()>, + exec: ThreadPool, + wake_handle: Arc, +} + +struct WakeHandle { + mutex: UnparkMutex, + exec: ThreadPool, +} + +impl Task { + /// Actually run the task (invoking `poll` on the future) on the current + /// thread. + fn run(self) { + let Self { mut future, wake_handle, mut exec } = self; + let waker = waker_ref(&wake_handle); + let mut cx = Context::from_waker(&waker); + + // Safety: The ownership of this `Task` object is evidence that + // we are in the `POLLING`/`REPOLL` state for the mutex. + unsafe { + wake_handle.mutex.start_poll(); + + loop { + let res = future.poll_unpin(&mut cx); + match res { + Poll::Pending => {} + Poll::Ready(()) => return wake_handle.mutex.complete(), + } + let task = Self { future, wake_handle: wake_handle.clone(), exec }; + match wake_handle.mutex.wait(task) { + Ok(()) => return, // we've waited + Err(task) => { + // someone's notified us + future = task.future; + exec = task.exec; + } + } + } + } + } +} + +impl fmt::Debug for Task { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Task").field("contents", &"...").finish() + } +} + +impl ArcWake for WakeHandle { + fn wake_by_ref(arc_self: &Arc) { + match arc_self.mutex.notify() { + Ok(task) => arc_self.exec.state.send(Message::Run(task)), + Err(()) => {} + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::mpsc; + + #[test] + fn test_drop_after_start() { + let (tx, rx) = mpsc::sync_channel(2); + let _cpu_pool = ThreadPoolBuilder::new() + .pool_size(2) + .after_start(move |_| tx.send(1).unwrap()) + .create() + .unwrap(); + + // After ThreadPoolBuilder is deconstructed, the tx should be droped + // so that we can use rx as an iterator. + let count = rx.into_iter().count(); + assert_eq!(count, 2); + } +} diff --git a/third_party/rust/futures-executor/src/unpark_mutex.rs b/third_party/rust/futures-executor/src/unpark_mutex.rs new file mode 100644 index 000000000000..ac5112cfa2ee --- /dev/null +++ b/third_party/rust/futures-executor/src/unpark_mutex.rs @@ -0,0 +1,137 @@ +use std::cell::UnsafeCell; +use std::sync::atomic::AtomicUsize; +use std::sync::atomic::Ordering::SeqCst; + +/// A "lock" around data `D`, which employs a *helping* strategy. +/// +/// Used to ensure that concurrent `unpark` invocations lead to (1) `poll` being +/// invoked on only a single thread at a time (2) `poll` being invoked at least +/// once after each `unpark` (unless the future has completed). +pub(crate) struct UnparkMutex { + // The state of task execution (state machine described below) + status: AtomicUsize, + + // The actual task data, accessible only in the POLLING state + inner: UnsafeCell>, +} + +// `UnparkMutex` functions in many ways like a `Mutex`, except that on +// acquisition failure, the current lock holder performs the desired work -- +// re-polling. +// +// As such, these impls mirror those for `Mutex`. In particular, a reference +// to `UnparkMutex` can be used to gain `&mut` access to the inner data, which +// must therefore be `Send`. +unsafe impl Send for UnparkMutex {} +unsafe impl Sync for UnparkMutex {} + +// There are four possible task states, listed below with their possible +// transitions: + +// The task is blocked, waiting on an event +const WAITING: usize = 0; // --> POLLING + +// The task is actively being polled by a thread; arrival of additional events +// of interest should move it to the REPOLL state +const POLLING: usize = 1; // --> WAITING, REPOLL, or COMPLETE + +// The task is actively being polled, but will need to be re-polled upon +// completion to ensure that all events were observed. +const REPOLL: usize = 2; // --> POLLING + +// The task has finished executing (either successfully or with an error/panic) +const COMPLETE: usize = 3; // No transitions out + +impl UnparkMutex { + pub(crate) fn new() -> Self { + Self { status: AtomicUsize::new(WAITING), inner: UnsafeCell::new(None) } + } + + /// Attempt to "notify" the mutex that a poll should occur. + /// + /// An `Ok` result indicates that the `POLLING` state has been entered, and + /// the caller can proceed to poll the future. An `Err` result indicates + /// that polling is not necessary (because the task is finished or the + /// polling has been delegated). + pub(crate) fn notify(&self) -> Result { + let mut status = self.status.load(SeqCst); + loop { + match status { + // The task is idle, so try to run it immediately. + WAITING => { + match self.status.compare_exchange(WAITING, POLLING, SeqCst, SeqCst) { + Ok(_) => { + let data = unsafe { + // SAFETY: we've ensured mutual exclusion via + // the status protocol; we are the only thread + // that has transitioned to the POLLING state, + // and we won't transition back to QUEUED until + // the lock is "released" by this thread. See + // the protocol diagram above. + (*self.inner.get()).take().unwrap() + }; + return Ok(data); + } + Err(cur) => status = cur, + } + } + + // The task is being polled, so we need to record that it should + // be *repolled* when complete. + POLLING => match self.status.compare_exchange(POLLING, REPOLL, SeqCst, SeqCst) { + Ok(_) => return Err(()), + Err(cur) => status = cur, + }, + + // The task is already scheduled for polling, or is complete, so + // we've got nothing to do. + _ => return Err(()), + } + } + } + + /// Alert the mutex that polling is about to begin, clearing any accumulated + /// re-poll requests. + /// + /// # Safety + /// + /// Callable only from the `POLLING`/`REPOLL` states, i.e. between + /// successful calls to `notify` and `wait`/`complete`. + pub(crate) unsafe fn start_poll(&self) { + self.status.store(POLLING, SeqCst); + } + + /// Alert the mutex that polling completed with `Pending`. + /// + /// # Safety + /// + /// Callable only from the `POLLING`/`REPOLL` states, i.e. between + /// successful calls to `notify` and `wait`/`complete`. + pub(crate) unsafe fn wait(&self, data: D) -> Result<(), D> { + *self.inner.get() = Some(data); + + match self.status.compare_exchange(POLLING, WAITING, SeqCst, SeqCst) { + // no unparks came in while we were running + Ok(_) => Ok(()), + + // guaranteed to be in REPOLL state; just clobber the + // state and run again. + Err(status) => { + assert_eq!(status, REPOLL); + self.status.store(POLLING, SeqCst); + Err((*self.inner.get()).take().unwrap()) + } + } + } + + /// Alert the mutex that the task has completed execution and should not be + /// notified again. + /// + /// # Safety + /// + /// Callable only from the `POLLING`/`REPOLL` states, i.e. between + /// successful calls to `notify` and `wait`/`complete`. + pub(crate) unsafe fn complete(&self) { + self.status.store(COMPLETE, SeqCst); + } +} diff --git a/third_party/rust/futures-executor/tests/local_pool.rs b/third_party/rust/futures-executor/tests/local_pool.rs new file mode 100644 index 000000000000..56e6daa07736 --- /dev/null +++ b/third_party/rust/futures-executor/tests/local_pool.rs @@ -0,0 +1,434 @@ +use futures::channel::oneshot; +use futures::executor::LocalPool; +use futures::future::{self, lazy, poll_fn, Future}; +use futures::task::{Context, LocalSpawn, Poll, Spawn, Waker}; +use std::cell::{Cell, RefCell}; +use std::pin::Pin; +use std::rc::Rc; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::thread; +use std::time::Duration; + +struct Pending(Rc<()>); + +impl Future for Pending { + type Output = (); + + fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> { + Poll::Pending + } +} + +fn pending() -> Pending { + Pending(Rc::new(())) +} + +#[test] +fn run_until_single_future() { + let mut cnt = 0; + + { + let mut pool = LocalPool::new(); + let fut = lazy(|_| { + cnt += 1; + }); + pool.run_until(fut); + } + + assert_eq!(cnt, 1); +} + +#[test] +fn run_until_ignores_spawned() { + let mut pool = LocalPool::new(); + let spawn = pool.spawner(); + spawn.spawn_local_obj(Box::pin(pending()).into()).unwrap(); + pool.run_until(lazy(|_| ())); +} + +#[test] +fn run_until_executes_spawned() { + let (tx, rx) = oneshot::channel(); + let mut pool = LocalPool::new(); + let spawn = pool.spawner(); + spawn + .spawn_local_obj( + Box::pin(lazy(move |_| { + tx.send(()).unwrap(); + })) + .into(), + ) + .unwrap(); + pool.run_until(rx).unwrap(); +} + +#[test] +fn run_returns_if_empty() { + let mut pool = LocalPool::new(); + pool.run(); + pool.run(); +} + +#[test] +fn run_executes_spawned() { + let cnt = Rc::new(Cell::new(0)); + let cnt2 = cnt.clone(); + + let mut pool = LocalPool::new(); + let spawn = pool.spawner(); + let spawn2 = pool.spawner(); + + spawn + .spawn_local_obj( + Box::pin(lazy(move |_| { + spawn2 + .spawn_local_obj( + Box::pin(lazy(move |_| { + cnt2.set(cnt2.get() + 1); + })) + .into(), + ) + .unwrap(); + })) + .into(), + ) + .unwrap(); + + pool.run(); + + assert_eq!(cnt.get(), 1); +} + +#[test] +fn run_spawn_many() { + const ITER: usize = 200; + + let cnt = Rc::new(Cell::new(0)); + + let mut pool = LocalPool::new(); + let spawn = pool.spawner(); + + for _ in 0..ITER { + let cnt = cnt.clone(); + spawn + .spawn_local_obj( + Box::pin(lazy(move |_| { + cnt.set(cnt.get() + 1); + })) + .into(), + ) + .unwrap(); + } + + pool.run(); + + assert_eq!(cnt.get(), ITER); +} + +#[test] +fn try_run_one_returns_if_empty() { + let mut pool = LocalPool::new(); + assert!(!pool.try_run_one()); +} + +#[test] +fn try_run_one_executes_one_ready() { + const ITER: usize = 200; + + let cnt = Rc::new(Cell::new(0)); + + let mut pool = LocalPool::new(); + let spawn = pool.spawner(); + + for _ in 0..ITER { + spawn.spawn_local_obj(Box::pin(pending()).into()).unwrap(); + + let cnt = cnt.clone(); + spawn + .spawn_local_obj( + Box::pin(lazy(move |_| { + cnt.set(cnt.get() + 1); + })) + .into(), + ) + .unwrap(); + + spawn.spawn_local_obj(Box::pin(pending()).into()).unwrap(); + } + + for i in 0..ITER { + assert_eq!(cnt.get(), i); + assert!(pool.try_run_one()); + assert_eq!(cnt.get(), i + 1); + } + assert!(!pool.try_run_one()); +} + +#[test] +fn try_run_one_returns_on_no_progress() { + const ITER: usize = 10; + + let cnt = Rc::new(Cell::new(0)); + + let mut pool = LocalPool::new(); + let spawn = pool.spawner(); + + let waker: Rc>> = Rc::new(Cell::new(None)); + { + let cnt = cnt.clone(); + let waker = waker.clone(); + spawn + .spawn_local_obj( + Box::pin(poll_fn(move |ctx| { + cnt.set(cnt.get() + 1); + waker.set(Some(ctx.waker().clone())); + if cnt.get() == ITER { + Poll::Ready(()) + } else { + Poll::Pending + } + })) + .into(), + ) + .unwrap(); + } + + for i in 0..ITER - 1 { + assert_eq!(cnt.get(), i); + assert!(!pool.try_run_one()); + assert_eq!(cnt.get(), i + 1); + let w = waker.take(); + assert!(w.is_some()); + w.unwrap().wake(); + } + assert!(pool.try_run_one()); + assert_eq!(cnt.get(), ITER); +} + +#[test] +fn try_run_one_runs_sub_futures() { + let mut pool = LocalPool::new(); + let spawn = pool.spawner(); + let cnt = Rc::new(Cell::new(0)); + + let inner_spawner = spawn.clone(); + let cnt1 = cnt.clone(); + spawn + .spawn_local_obj( + Box::pin(poll_fn(move |_| { + cnt1.set(cnt1.get() + 1); + + let cnt2 = cnt1.clone(); + inner_spawner + .spawn_local_obj(Box::pin(lazy(move |_| cnt2.set(cnt2.get() + 1))).into()) + .unwrap(); + + Poll::Pending + })) + .into(), + ) + .unwrap(); + + pool.try_run_one(); + assert_eq!(cnt.get(), 2); +} + +#[test] +fn run_until_stalled_returns_if_empty() { + let mut pool = LocalPool::new(); + pool.run_until_stalled(); + pool.run_until_stalled(); +} + +#[test] +fn run_until_stalled_returns_multiple_times() { + let mut pool = LocalPool::new(); + let spawn = pool.spawner(); + let cnt = Rc::new(Cell::new(0)); + + let cnt1 = cnt.clone(); + spawn.spawn_local_obj(Box::pin(lazy(move |_| cnt1.set(cnt1.get() + 1))).into()).unwrap(); + pool.run_until_stalled(); + assert_eq!(cnt.get(), 1); + + let cnt2 = cnt.clone(); + spawn.spawn_local_obj(Box::pin(lazy(move |_| cnt2.set(cnt2.get() + 1))).into()).unwrap(); + pool.run_until_stalled(); + assert_eq!(cnt.get(), 2); +} + +#[test] +fn run_until_stalled_runs_spawned_sub_futures() { + let mut pool = LocalPool::new(); + let spawn = pool.spawner(); + let cnt = Rc::new(Cell::new(0)); + + let inner_spawner = spawn.clone(); + let cnt1 = cnt.clone(); + spawn + .spawn_local_obj( + Box::pin(poll_fn(move |_| { + cnt1.set(cnt1.get() + 1); + + let cnt2 = cnt1.clone(); + inner_spawner + .spawn_local_obj(Box::pin(lazy(move |_| cnt2.set(cnt2.get() + 1))).into()) + .unwrap(); + + Poll::Pending + })) + .into(), + ) + .unwrap(); + + pool.run_until_stalled(); + assert_eq!(cnt.get(), 2); +} + +#[test] +fn run_until_stalled_executes_all_ready() { + const ITER: usize = 200; + const PER_ITER: usize = 3; + + let cnt = Rc::new(Cell::new(0)); + + let mut pool = LocalPool::new(); + let spawn = pool.spawner(); + + for i in 0..ITER { + for _ in 0..PER_ITER { + spawn.spawn_local_obj(Box::pin(pending()).into()).unwrap(); + + let cnt = cnt.clone(); + spawn + .spawn_local_obj( + Box::pin(lazy(move |_| { + cnt.set(cnt.get() + 1); + })) + .into(), + ) + .unwrap(); + + // also add some pending tasks to test if they are ignored + spawn.spawn_local_obj(Box::pin(pending()).into()).unwrap(); + } + assert_eq!(cnt.get(), i * PER_ITER); + pool.run_until_stalled(); + assert_eq!(cnt.get(), (i + 1) * PER_ITER); + } +} + +#[test] +#[should_panic] +fn nesting_run() { + let mut pool = LocalPool::new(); + let spawn = pool.spawner(); + + spawn + .spawn_obj( + Box::pin(lazy(|_| { + let mut pool = LocalPool::new(); + pool.run(); + })) + .into(), + ) + .unwrap(); + + pool.run(); +} + +#[test] +#[should_panic] +fn nesting_run_run_until_stalled() { + let mut pool = LocalPool::new(); + let spawn = pool.spawner(); + + spawn + .spawn_obj( + Box::pin(lazy(|_| { + let mut pool = LocalPool::new(); + pool.run_until_stalled(); + })) + .into(), + ) + .unwrap(); + + pool.run(); +} + +#[test] +fn tasks_are_scheduled_fairly() { + let state = Rc::new(RefCell::new([0, 0])); + + struct Spin { + state: Rc>, + idx: usize, + } + + impl Future for Spin { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { + let mut state = self.state.borrow_mut(); + + if self.idx == 0 { + let diff = state[0] - state[1]; + + assert!(diff.abs() <= 1); + + if state[0] >= 50 { + return Poll::Ready(()); + } + } + + state[self.idx] += 1; + + if state[self.idx] >= 100 { + return Poll::Ready(()); + } + + cx.waker().wake_by_ref(); + Poll::Pending + } + } + + let mut pool = LocalPool::new(); + let spawn = pool.spawner(); + + spawn.spawn_local_obj(Box::pin(Spin { state: state.clone(), idx: 0 }).into()).unwrap(); + + spawn.spawn_local_obj(Box::pin(Spin { state, idx: 1 }).into()).unwrap(); + + pool.run(); +} + +// Tests that the use of park/unpark in user-code has no +// effect on the expected behaviour of the executor. +#[test] +fn park_unpark_independence() { + let mut done = false; + + let future = future::poll_fn(move |cx| { + if done { + return Poll::Ready(()); + } + done = true; + cx.waker().clone().wake(); // (*) + // some user-code that temporarily parks the thread + let test = thread::current(); + let latch = Arc::new(AtomicBool::new(false)); + let signal = latch.clone(); + thread::spawn(move || { + thread::sleep(Duration::from_millis(10)); + signal.store(true, Ordering::SeqCst); + test.unpark() + }); + while !latch.load(Ordering::Relaxed) { + thread::park(); + } + Poll::Pending // Expect to be called again due to (*). + }); + + futures::executor::block_on(future) +} diff --git a/third_party/rust/futures-macro/.cargo-checksum.json b/third_party/rust/futures-macro/.cargo-checksum.json new file mode 100644 index 000000000000..cce5bd9fc8f1 --- /dev/null +++ b/third_party/rust/futures-macro/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"e880238cae1360867ddb0456ec73c688d36a0f2c5261bc958bd0c027c44ed797","LICENSE-APACHE":"275c491d6d1160553c32fd6127061d7f9606c3ea25abfad6ca3f6ed088785427","LICENSE-MIT":"6652c868f35dfe5e8ef636810a4e576b9d663f3a17fb0f5613ad73583e1b88fd","build.rs":"d3c815ba8084078e529ecf5a1232df2995bed4f2ff2c408d16c1f2388c39081a","src/executor.rs":"2a6c40ebf1fb70ac5bd0dfb991c7b945210c731b558b546f2ecb6d7a8976f3f6","src/join.rs":"e0d286558bd944fd02c1bd2501d13e62de2aa65e6bd3a2e0567488ac1a2374ed","src/lib.rs":"aeb54392a278117b959e1d49604ea1873438a77025dca9a4c36b03bd7e8522f4","src/select.rs":"a7ed344932225fbe1b070d132a937184250c31385ac6764a8a6e6817413c7538"},"package":"a4c40298486cdf52cc00cd6d6987892ba502c7656a16a4192a9992b1ccedd121"} \ No newline at end of file diff --git a/third_party/rust/futures-macro/Cargo.toml b/third_party/rust/futures-macro/Cargo.toml new file mode 100644 index 000000000000..fc6cfc861449 --- /dev/null +++ b/third_party/rust/futures-macro/Cargo.toml @@ -0,0 +1,41 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies +# +# If you believe there's an error in this file please file an +# issue against the rust-lang/cargo repository. If you're +# editing this file be aware that the upstream Cargo.toml +# will likely look very different (and much more reasonable) + +[package] +edition = "2018" +name = "futures-macro" +version = "0.3.15" +authors = ["Taylor Cramer ", "Taiki Endo "] +description = "The futures-rs procedural macro implementations.\n" +homepage = "https://rust-lang.github.io/futures-rs" +documentation = "https://docs.rs/futures-macro/0.3" +license = "MIT OR Apache-2.0" +repository = "https://github.com/rust-lang/futures-rs" + +[lib] +proc-macro = true +[dependencies.proc-macro-hack] +version = "0.5.19" + +[dependencies.proc-macro2] +version = "1.0" + +[dependencies.quote] +version = "1.0" + +[dependencies.syn] +version = "1.0.56" +features = ["full"] +[build-dependencies.autocfg] +version = "1" + +[features] diff --git a/third_party/rust/futures-macro/LICENSE-APACHE b/third_party/rust/futures-macro/LICENSE-APACHE new file mode 100644 index 000000000000..9eb0b097f5d0 --- /dev/null +++ b/third_party/rust/futures-macro/LICENSE-APACHE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright (c) 2016 Alex Crichton +Copyright (c) 2017 The Tokio Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/third_party/rust/futures-macro/LICENSE-MIT b/third_party/rust/futures-macro/LICENSE-MIT new file mode 100644 index 000000000000..8ad082ec4f93 --- /dev/null +++ b/third_party/rust/futures-macro/LICENSE-MIT @@ -0,0 +1,26 @@ +Copyright (c) 2016 Alex Crichton +Copyright (c) 2017 The Tokio Authors + +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/third_party/rust/futures-macro/build.rs b/third_party/rust/futures-macro/build.rs new file mode 100644 index 000000000000..ff8630ce69bd --- /dev/null +++ b/third_party/rust/futures-macro/build.rs @@ -0,0 +1,28 @@ +#![warn(rust_2018_idioms, single_use_lifetimes)] + +use autocfg::AutoCfg; + +// The rustc-cfg strings below are *not* public API. Please let us know by +// opening a GitHub issue if your build environment requires some way to enable +// these cfgs other than by executing our build script. +fn main() { + let cfg = match AutoCfg::new() { + Ok(cfg) => cfg, + Err(e) => { + println!( + "cargo:warning={}: unable to determine rustc version: {}", + env!("CARGO_PKG_NAME"), + e + ); + return; + } + }; + + // Function like procedural macros in expressions patterns statements stabilized in Rust 1.45: + // https://blog.rust-lang.org/2020/07/16/Rust-1.45.0.html#stabilizing-function-like-procedural-macros-in-expressions-patterns-and-statements + if cfg.probe_rustc_version(1, 45) { + println!("cargo:rustc-cfg=fn_like_proc_macro"); + } + + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/third_party/rust/futures-macro/src/executor.rs b/third_party/rust/futures-macro/src/executor.rs new file mode 100644 index 000000000000..40a091f94c61 --- /dev/null +++ b/third_party/rust/futures-macro/src/executor.rs @@ -0,0 +1,55 @@ +use proc_macro::TokenStream; +use proc_macro2::Span; +use quote::{quote, quote_spanned, ToTokens}; + +pub(crate) fn test(args: TokenStream, item: TokenStream) -> TokenStream { + if !args.is_empty() { + return syn::Error::new_spanned(proc_macro2::TokenStream::from(args), "invalid argument") + .to_compile_error() + .into(); + } + + let mut input = syn::parse_macro_input!(item as syn::ItemFn); + + if input.sig.asyncness.take().is_none() { + return syn::Error::new_spanned(input.sig.fn_token, "Only async functions are supported") + .to_compile_error() + .into(); + } + + // If type mismatch occurs, the current rustc points to the last statement. + let (last_stmt_start_span, last_stmt_end_span) = { + let mut last_stmt = input + .block + .stmts + .last() + .map(ToTokens::into_token_stream) + .unwrap_or_default() + .into_iter(); + // `Span` on stable Rust has a limitation that only points to the first + // token, not the whole tokens. We can work around this limitation by + // using the first/last span of the tokens like + // `syn::Error::new_spanned` does. + let start = last_stmt.next().map_or_else(Span::call_site, |t| t.span()); + let end = last_stmt.last().map_or(start, |t| t.span()); + (start, end) + }; + + let path = quote_spanned! {last_stmt_start_span=> + ::futures_test::__private + }; + let body = &input.block; + input.block.stmts = vec![syn::Stmt::Expr( + syn::parse2(quote_spanned! {last_stmt_end_span=> + #path::block_on(async #body) + }) + .unwrap(), + )]; + + let gen = quote! { + #[::core::prelude::v1::test] + #input + }; + + gen.into() +} diff --git a/third_party/rust/futures-macro/src/join.rs b/third_party/rust/futures-macro/src/join.rs new file mode 100644 index 000000000000..d427da27a09f --- /dev/null +++ b/third_party/rust/futures-macro/src/join.rs @@ -0,0 +1,143 @@ +//! The futures-rs `join! macro implementation. + +use proc_macro::TokenStream; +use proc_macro2::{Span, TokenStream as TokenStream2}; +use quote::{format_ident, quote}; +use syn::parse::{Parse, ParseStream}; +use syn::{Expr, Ident, Token}; + +#[derive(Default)] +struct Join { + fut_exprs: Vec, +} + +impl Parse for Join { + fn parse(input: ParseStream<'_>) -> syn::Result { + let mut join = Self::default(); + + while !input.is_empty() { + join.fut_exprs.push(input.parse::()?); + + if !input.is_empty() { + input.parse::()?; + } + } + + Ok(join) + } +} + +fn bind_futures(fut_exprs: Vec, span: Span) -> (Vec, Vec) { + let mut future_let_bindings = Vec::with_capacity(fut_exprs.len()); + let future_names: Vec<_> = fut_exprs + .into_iter() + .enumerate() + .map(|(i, expr)| { + let name = format_ident!("_fut{}", i, span = span); + future_let_bindings.push(quote! { + // Move future into a local so that it is pinned in one place and + // is no longer accessible by the end user. + let mut #name = __futures_crate::future::maybe_done(#expr); + }); + name + }) + .collect(); + + (future_let_bindings, future_names) +} + +/// The `join!` macro. +pub(crate) fn join(input: TokenStream) -> TokenStream { + let parsed = syn::parse_macro_input!(input as Join); + + // should be def_site, but that's unstable + let span = Span::call_site(); + + let (future_let_bindings, future_names) = bind_futures(parsed.fut_exprs, span); + + let poll_futures = future_names.iter().map(|fut| { + quote! { + __all_done &= __futures_crate::future::Future::poll( + unsafe { __futures_crate::Pin::new_unchecked(&mut #fut) }, __cx).is_ready(); + } + }); + let take_outputs = future_names.iter().map(|fut| { + quote! { + unsafe { __futures_crate::Pin::new_unchecked(&mut #fut) }.take_output().unwrap(), + } + }); + + TokenStream::from(quote! { { + #( #future_let_bindings )* + + __futures_crate::future::poll_fn(move |__cx: &mut __futures_crate::task::Context<'_>| { + let mut __all_done = true; + #( #poll_futures )* + if __all_done { + __futures_crate::task::Poll::Ready(( + #( #take_outputs )* + )) + } else { + __futures_crate::task::Poll::Pending + } + }).await + } }) +} + +/// The `try_join!` macro. +pub(crate) fn try_join(input: TokenStream) -> TokenStream { + let parsed = syn::parse_macro_input!(input as Join); + + // should be def_site, but that's unstable + let span = Span::call_site(); + + let (future_let_bindings, future_names) = bind_futures(parsed.fut_exprs, span); + + let poll_futures = future_names.iter().map(|fut| { + quote! { + if __futures_crate::future::Future::poll( + unsafe { __futures_crate::Pin::new_unchecked(&mut #fut) }, __cx).is_pending() + { + __all_done = false; + } else if unsafe { __futures_crate::Pin::new_unchecked(&mut #fut) }.output_mut().unwrap().is_err() { + // `.err().unwrap()` rather than `.unwrap_err()` so that we don't introduce + // a `T: Debug` bound. + // Also, for an error type of ! any code after `err().unwrap()` is unreachable. + #[allow(unreachable_code)] + return __futures_crate::task::Poll::Ready( + __futures_crate::Err( + unsafe { __futures_crate::Pin::new_unchecked(&mut #fut) }.take_output().unwrap().err().unwrap() + ) + ); + } + } + }); + let take_outputs = future_names.iter().map(|fut| { + quote! { + // `.ok().unwrap()` rather than `.unwrap()` so that we don't introduce + // an `E: Debug` bound. + // Also, for an ok type of ! any code after `ok().unwrap()` is unreachable. + #[allow(unreachable_code)] + unsafe { __futures_crate::Pin::new_unchecked(&mut #fut) }.take_output().unwrap().ok().unwrap(), + } + }); + + TokenStream::from(quote! { { + #( #future_let_bindings )* + + #[allow(clippy::diverging_sub_expression)] + __futures_crate::future::poll_fn(move |__cx: &mut __futures_crate::task::Context<'_>| { + let mut __all_done = true; + #( #poll_futures )* + if __all_done { + __futures_crate::task::Poll::Ready( + __futures_crate::Ok(( + #( #take_outputs )* + )) + ) + } else { + __futures_crate::task::Poll::Pending + } + }).await + } }) +} diff --git a/third_party/rust/futures-macro/src/lib.rs b/third_party/rust/futures-macro/src/lib.rs new file mode 100644 index 000000000000..8835de4fa8b9 --- /dev/null +++ b/third_party/rust/futures-macro/src/lib.rs @@ -0,0 +1,54 @@ +//! The futures-rs procedural macro implementations. + +#![recursion_limit = "128"] +#![warn(rust_2018_idioms, unreachable_pub)] +// It cannot be included in the published code because this lints have false positives in the minimum required version. +#![cfg_attr(test, warn(single_use_lifetimes))] +#![warn(clippy::all)] +#![doc(test(attr(deny(warnings), allow(dead_code, unused_assignments, unused_variables))))] + +// Since https://github.com/rust-lang/cargo/pull/7700 `proc_macro` is part of the prelude for +// proc-macro crates, but to support older compilers we still need this explicit `extern crate`. +#[allow(unused_extern_crates)] +extern crate proc_macro; + +use proc_macro::TokenStream; + +mod executor; +mod join; +mod select; + +/// The `join!` macro. +#[cfg_attr(fn_like_proc_macro, proc_macro)] +#[cfg_attr(not(fn_like_proc_macro), proc_macro_hack::proc_macro_hack)] +pub fn join_internal(input: TokenStream) -> TokenStream { + crate::join::join(input) +} + +/// The `try_join!` macro. +#[cfg_attr(fn_like_proc_macro, proc_macro)] +#[cfg_attr(not(fn_like_proc_macro), proc_macro_hack::proc_macro_hack)] +pub fn try_join_internal(input: TokenStream) -> TokenStream { + crate::join::try_join(input) +} + +/// The `select!` macro. +#[cfg_attr(fn_like_proc_macro, proc_macro)] +#[cfg_attr(not(fn_like_proc_macro), proc_macro_hack::proc_macro_hack)] +pub fn select_internal(input: TokenStream) -> TokenStream { + crate::select::select(input) +} + +/// The `select_biased!` macro. +#[cfg_attr(fn_like_proc_macro, proc_macro)] +#[cfg_attr(not(fn_like_proc_macro), proc_macro_hack::proc_macro_hack)] +pub fn select_biased_internal(input: TokenStream) -> TokenStream { + crate::select::select_biased(input) +} + +// TODO: Change this to doc comment once rustdoc bug fixed. +// The `test` attribute. +#[proc_macro_attribute] +pub fn test_internal(input: TokenStream, item: TokenStream) -> TokenStream { + crate::executor::test(input, item) +} diff --git a/third_party/rust/futures-macro/src/select.rs b/third_party/rust/futures-macro/src/select.rs new file mode 100644 index 000000000000..0c8e5f1ca0c4 --- /dev/null +++ b/third_party/rust/futures-macro/src/select.rs @@ -0,0 +1,330 @@ +//! The futures-rs `select! macro implementation. + +use proc_macro::TokenStream; +use proc_macro2::Span; +use quote::{format_ident, quote}; +use syn::parse::{Parse, ParseStream}; +use syn::{parse_quote, Expr, Ident, Pat, Token}; + +mod kw { + syn::custom_keyword!(complete); +} + +struct Select { + // span of `complete`, then expression after `=> ...` + complete: Option, + default: Option, + normal_fut_exprs: Vec, + normal_fut_handlers: Vec<(Pat, Expr)>, +} + +#[allow(clippy::large_enum_variant)] +enum CaseKind { + Complete, + Default, + Normal(Pat, Expr), +} + +impl Parse for Select { + fn parse(input: ParseStream<'_>) -> syn::Result { + let mut select = Self { + complete: None, + default: None, + normal_fut_exprs: vec![], + normal_fut_handlers: vec![], + }; + + while !input.is_empty() { + let case_kind = if input.peek(kw::complete) { + // `complete` + if select.complete.is_some() { + return Err(input.error("multiple `complete` cases found, only one allowed")); + } + input.parse::()?; + CaseKind::Complete + } else if input.peek(Token![default]) { + // `default` + if select.default.is_some() { + return Err(input.error("multiple `default` cases found, only one allowed")); + } + input.parse::()?; + CaseKind::Default + } else { + // ` = ` + let pat = input.parse()?; + input.parse::()?; + let expr = input.parse()?; + CaseKind::Normal(pat, expr) + }; + + // `=> ` + input.parse::]>()?; + let expr = input.parse::()?; + + // Commas after the expression are only optional if it's a `Block` + // or it is the last branch in the `match`. + let is_block = match expr { + Expr::Block(_) => true, + _ => false, + }; + if is_block || input.is_empty() { + input.parse::>()?; + } else { + input.parse::()?; + } + + match case_kind { + CaseKind::Complete => select.complete = Some(expr), + CaseKind::Default => select.default = Some(expr), + CaseKind::Normal(pat, fut_expr) => { + select.normal_fut_exprs.push(fut_expr); + select.normal_fut_handlers.push((pat, expr)); + } + } + } + + Ok(select) + } +} + +// Enum over all the cases in which the `select!` waiting has completed and the result +// can be processed. +// +// `enum __PrivResult<_1, _2, ...> { _1(_1), _2(_2), ..., Complete }` +fn declare_result_enum( + result_ident: Ident, + variants: usize, + complete: bool, + span: Span, +) -> (Vec, syn::ItemEnum) { + // "_0", "_1", "_2" + let variant_names: Vec = + (0..variants).map(|num| format_ident!("_{}", num, span = span)).collect(); + + let type_parameters = &variant_names; + let variants = &variant_names; + + let complete_variant = if complete { Some(quote!(Complete)) } else { None }; + + let enum_item = parse_quote! { + enum #result_ident<#(#type_parameters,)*> { + #( + #variants(#type_parameters), + )* + #complete_variant + } + }; + + (variant_names, enum_item) +} + +/// The `select!` macro. +pub(crate) fn select(input: TokenStream) -> TokenStream { + select_inner(input, true) +} + +/// The `select_biased!` macro. +pub(crate) fn select_biased(input: TokenStream) -> TokenStream { + select_inner(input, false) +} + +fn select_inner(input: TokenStream, random: bool) -> TokenStream { + let parsed = syn::parse_macro_input!(input as Select); + + // should be def_site, but that's unstable + let span = Span::call_site(); + + let enum_ident = Ident::new("__PrivResult", span); + + let (variant_names, enum_item) = declare_result_enum( + enum_ident.clone(), + parsed.normal_fut_exprs.len(), + parsed.complete.is_some(), + span, + ); + + // bind non-`Ident` future exprs w/ `let` + let mut future_let_bindings = Vec::with_capacity(parsed.normal_fut_exprs.len()); + let bound_future_names: Vec<_> = parsed + .normal_fut_exprs + .into_iter() + .zip(variant_names.iter()) + .map(|(expr, variant_name)| { + match expr { + syn::Expr::Path(path) => { + // Don't bind futures that are already a path. + // This prevents creating redundant stack space + // for them. + // Passing Futures by path requires those Futures to implement Unpin. + // We check for this condition here in order to be able to + // safely use Pin::new_unchecked(&mut #path) later on. + future_let_bindings.push(quote! { + __futures_crate::async_await::assert_fused_future(&#path); + __futures_crate::async_await::assert_unpin(&#path); + }); + path + } + _ => { + // Bind and pin the resulting Future on the stack. This is + // necessary to support direct select! calls on !Unpin + // Futures. The Future is not explicitly pinned here with + // a Pin call, but assumed as pinned. The actual Pin is + // created inside the poll() function below to defer the + // creation of the temporary pointer, which would otherwise + // increase the size of the generated Future. + // Safety: This is safe since the lifetime of the Future + // is totally constraint to the lifetime of the select! + // expression, and the Future can't get moved inside it + // (it is shadowed). + future_let_bindings.push(quote! { + let mut #variant_name = #expr; + }); + parse_quote! { #variant_name } + } + } + }) + .collect(); + + // For each future, make an `&mut dyn FnMut(&mut Context<'_>) -> Option>` + // to use for polling that individual future. These will then be put in an array. + let poll_functions = bound_future_names.iter().zip(variant_names.iter()).map( + |(bound_future_name, variant_name)| { + // Below we lazily create the Pin on the Future below. + // This is done in order to avoid allocating memory in the generator + // for the Pin variable. + // Safety: This is safe because one of the following condition applies: + // 1. The Future is passed by the caller by name, and we assert that + // it implements Unpin. + // 2. The Future is created in scope of the select! function and will + // not be moved for the duration of it. It is thereby stack-pinned + quote! { + let mut #variant_name = |__cx: &mut __futures_crate::task::Context<'_>| { + let mut #bound_future_name = unsafe { + __futures_crate::Pin::new_unchecked(&mut #bound_future_name) + }; + if __futures_crate::future::FusedFuture::is_terminated(&#bound_future_name) { + __futures_crate::None + } else { + __futures_crate::Some(__futures_crate::future::FutureExt::poll_unpin( + &mut #bound_future_name, + __cx, + ).map(#enum_ident::#variant_name)) + } + }; + let #variant_name: &mut dyn FnMut( + &mut __futures_crate::task::Context<'_> + ) -> __futures_crate::Option<__futures_crate::task::Poll<_>> = &mut #variant_name; + } + }, + ); + + let none_polled = if parsed.complete.is_some() { + quote! { + __futures_crate::task::Poll::Ready(#enum_ident::Complete) + } + } else { + quote! { + panic!("all futures in select! were completed,\ + but no `complete =>` handler was provided") + } + }; + + let branches = parsed.normal_fut_handlers.into_iter().zip(variant_names.iter()).map( + |((pat, expr), variant_name)| { + quote! { + #enum_ident::#variant_name(#pat) => { #expr }, + } + }, + ); + let branches = quote! { #( #branches )* }; + + let complete_branch = parsed.complete.map(|complete_expr| { + quote! { + #enum_ident::Complete => { #complete_expr }, + } + }); + + let branches = quote! { + #branches + #complete_branch + }; + + let await_select_fut = if parsed.default.is_some() { + // For select! with default this returns the Poll result + quote! { + __poll_fn(&mut __futures_crate::task::Context::from_waker( + __futures_crate::task::noop_waker_ref() + )) + } + } else { + quote! { + __futures_crate::future::poll_fn(__poll_fn).await + } + }; + + let execute_result_expr = if let Some(default_expr) = &parsed.default { + // For select! with default __select_result is a Poll, otherwise not + quote! { + match __select_result { + __futures_crate::task::Poll::Ready(result) => match result { + #branches + }, + _ => #default_expr + } + } + } else { + quote! { + match __select_result { + #branches + } + } + }; + + let shuffle = if random { + quote! { + __futures_crate::async_await::shuffle(&mut __select_arr); + } + } else { + quote!() + }; + + TokenStream::from(quote! { { + #enum_item + + let __select_result = { + #( #future_let_bindings )* + + let mut __poll_fn = |__cx: &mut __futures_crate::task::Context<'_>| { + let mut __any_polled = false; + + #( #poll_functions )* + + let mut __select_arr = [#( #variant_names ),*]; + #shuffle + for poller in &mut __select_arr { + let poller: &mut &mut dyn FnMut( + &mut __futures_crate::task::Context<'_> + ) -> __futures_crate::Option<__futures_crate::task::Poll<_>> = poller; + match poller(__cx) { + __futures_crate::Some(x @ __futures_crate::task::Poll::Ready(_)) => + return x, + __futures_crate::Some(__futures_crate::task::Poll::Pending) => { + __any_polled = true; + } + __futures_crate::None => {} + } + } + + if !__any_polled { + #none_polled + } else { + __futures_crate::task::Poll::Pending + } + }; + + #await_select_fut + }; + + #execute_result_expr + } }) +} diff --git a/third_party/rust/l10nregistry/.cargo-checksum.json b/third_party/rust/l10nregistry/.cargo-checksum.json new file mode 100644 index 000000000000..16aef1d533ce --- /dev/null +++ b/third_party/rust/l10nregistry/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"5874fa7dbce795209a9312250fee6db8c807ddfa53df4926bebcc9b718bc2281","LICENSE-APACHE":"5db2b182453ff32ed40f7da63589c9667a3f8bd8b16b1471b152caae56f77e45","LICENSE-MIT":"49c0b000c03731d9e3970dc059ad4ca345d773681f4a612b0024435b663e0220","benches/localization.rs":"879b46b5cc6b9c8bade51796acb0de0c5dbaa197c24b644592db06f976d39208","benches/preferences.rs":"8f5242b2166896da6219be57c5cb48f5b995a884b3bafa08358ce5206b3ad098","benches/solver.rs":"dbffd67c583b76b64499b9bf5163e40b7238afafa7edee5aabb2f04e956a15a6","benches/source.rs":"5efb5e562509aeab6a643739251d2b31994e0240316ecf407ca2188f14cbcf5d","src/env.rs":"213679dd9ef5d2aa8049f0351c2caa00e8890f788ff8a237cd8fccb01d061cdb","src/errors.rs":"f025bd1674c16b85b3097bf86831f0e8a0860f00b7151293e94206e456bd6959","src/fluent.rs":"ccf3ed58e1281e28ab36e75157778452d7c5323f5b08ff48fbea7bb45b70d361","src/lib.rs":"c2bba574aa4c1258d88695f7410175ccf04c3adf1ea3fb10769a0c9f4e07b133","src/registry/asynchronous.rs":"a392c5463caf7eeb9dc27c311d08361337b14fc6b05e83fedbe5683c66967a94","src/registry/mod.rs":"8b5077b30d3a0bde13270552009eaad0aece9c447f3c5fe210b56a1aedc6cddf","src/registry/synchronous.rs":"2c5be6ed23c2b365534d721e88e7a4bae78df22e3c1aeb70172393a226d01d97","src/solver/README.md":"bd93b3faef6adec5e71f06a7e06ca64b32f3877b513de099638fa86acf1e2d9e","src/solver/mod.rs":"f763519feca67ce6ce42c4387482fb10313f9f33f7d0fe687e0db51ef9592a6b","src/solver/parallel.rs":"c4c4edc38d8f639b0df057c12e0f4f0e0026e3f2fb3ad9563689d2620dd99fea","src/solver/serial.rs":"dcac1128fc87295b955c11b40b7adca5106a585de9adc2e4361cc6a92c968e27","src/solver/testing/mod.rs":"9f8b3d5b3436088d410d5544d2c2e33320bb56b0d364f1862790e4ab66fb7764","src/solver/testing/scenarios.rs":"2a11b2f3a6bbfe0055ea6125cae498391e28a4811c019e2f9479f959798e5767","src/source/fetcher.rs":"6220bf5010df94eb92f1adc56f76a65b41227b59312e27963d75b16ea0ee6393","src/source/mod.rs":"3e507f5775f71abc55acdd3e9a4be34f241c4e889244caae0142e0bc2ecae109","src/testing.rs":"4408fc78efb0d3dd52d4f8cb669914ef96531fd94796a5a2f4e1f65bc54f0e5f","tests/localization.rs":"5c2f4e75f03d4e42880adde4a6902e60b0c87d14160663e495cc6ed3091b4285","tests/registry.rs":"f9da785b408f0d54b2ca8f0bd7f52e86e75e9a642fbb91f57be870160024cbb6","tests/scenarios.rs":"ffe16cb382895bdbe809f63233fc1ac9a4be9022bc7b60d2f3210b49653569e8","tests/source.rs":"c43bbcc6f2e0c34766785cd1be94a3ed34f7f6327a1693f5dce62bcae7ac1d39"},"package":null} \ No newline at end of file diff --git a/third_party/rust/l10nregistry/Cargo.toml b/third_party/rust/l10nregistry/Cargo.toml new file mode 100644 index 000000000000..2d301ccc7f79 --- /dev/null +++ b/third_party/rust/l10nregistry/Cargo.toml @@ -0,0 +1,63 @@ +[package] +name = "l10nregistry" +version = "0.2.0" +authors = ["Zibi Braniecki "] +license = "Apache-2.0/MIT" +edition = "2018" + +[dependencies] +async-trait = "0.1" +fluent-bundle = "0.15" +fluent-fallback = "0.5" +fluent-testing = { git = "https://github.com/projectfluent/fluent-rs", optional = true, features = ["sync", "async"] } +futures = "0.3" +pin-project-lite = "0.2" +unic-langid = "0.9" +tokio = { version = "1.0", optional = true, features = ["rt-multi-thread", "macros"] } +replace_with = "0.1" +rustc-hash = "1" + +[dev-dependencies] +unic-langid = { version = "0.9", features = ["macros"] } +serial_test = "0.5" +criterion = "0.3" + +[features] +default = [] +tokio-io = ["tokio"] + +[[bench]] +name = "preferences" +harness = false + +[[bench]] +name = "localization" +harness = false + +[[bench]] +name = "source" +harness = false + +[[bench]] +name = "solver" +harness = false + +[[test]] +name = "source" +path = "tests/source.rs" +required-features = ["tokio", "fluent-testing"] + +[[test]] +name = "registry" +path = "tests/registry.rs" +required-features = ["tokio", "fluent-testing"] + +[[test]] +name = "localization" +path = "tests/localization.rs" +required-features = ["tokio", "fluent-testing"] + +[[test]] +name = "scenarios" +path = "tests/scenarios.rs" +required-features = ["tokio", "fluent-testing"] diff --git a/third_party/rust/l10nregistry/LICENSE-APACHE b/third_party/rust/l10nregistry/LICENSE-APACHE new file mode 100644 index 000000000000..35582f166b4b --- /dev/null +++ b/third_party/rust/l10nregistry/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2017 Mozilla + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/third_party/rust/l10nregistry/LICENSE-MIT b/third_party/rust/l10nregistry/LICENSE-MIT new file mode 100644 index 000000000000..5655fa311cd2 --- /dev/null +++ b/third_party/rust/l10nregistry/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright 2017 Mozilla + +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/third_party/rust/l10nregistry/benches/localization.rs b/third_party/rust/l10nregistry/benches/localization.rs new file mode 100644 index 000000000000..696cc244ed64 --- /dev/null +++ b/third_party/rust/l10nregistry/benches/localization.rs @@ -0,0 +1,70 @@ +use criterion::criterion_group; +use criterion::criterion_main; +use criterion::Criterion; + +use fluent_bundle::FluentArgs; +use fluent_fallback::{types::L10nKey, Localization}; +use fluent_testing::get_scenarios; +use l10nregistry::testing::TestFileFetcher; + +fn preferences_bench(c: &mut Criterion) { + let fetcher = TestFileFetcher::new(); + + let mut group = c.benchmark_group("localization/scenarios"); + + for scenario in get_scenarios() { + let res_ids = scenario.res_ids.clone(); + let l10n_keys: Vec<(String, Option)> = scenario + .queries + .iter() + .map(|q| { + ( + q.input.id.clone(), + q.input.args.as_ref().map(|args| { + let mut result = FluentArgs::new(); + for arg in args.as_slice() { + result.set(arg.id.clone(), arg.value.clone()); + } + result + }), + ) + }) + .collect(); + + group.bench_function(format!("{}/format_value_sync", scenario.name), |b| { + b.iter(|| { + let (env, reg) = fetcher.get_registry_and_environment(&scenario); + let mut errors = vec![]; + + let loc = Localization::with_env(res_ids.clone(), true, env.clone(), reg.clone()); + let bundles = loc.bundles(); + + for key in l10n_keys.iter() { + bundles.format_value_sync(&key.0, key.1.as_ref(), &mut errors); + } + }) + }); + + let keys: Vec = l10n_keys + .into_iter() + .map(|key| L10nKey { + id: key.0.into(), + args: key.1, + }) + .collect(); + group.bench_function(format!("{}/format_messages_sync", scenario.name), |b| { + b.iter(|| { + let (env, reg) = fetcher.get_registry_and_environment(&scenario); + let mut errors = vec![]; + let loc = Localization::with_env(res_ids.clone(), true, env.clone(), reg.clone()); + let bundles = loc.bundles(); + bundles.format_messages_sync(&keys, &mut errors); + }) + }); + } + + group.finish(); +} + +criterion_group!(benches, preferences_bench); +criterion_main!(benches); diff --git a/third_party/rust/l10nregistry/benches/preferences.rs b/third_party/rust/l10nregistry/benches/preferences.rs new file mode 100644 index 000000000000..4fcfc2dcf35d --- /dev/null +++ b/third_party/rust/l10nregistry/benches/preferences.rs @@ -0,0 +1,57 @@ +use criterion::criterion_group; +use criterion::criterion_main; +use criterion::Criterion; + +use fluent_testing::get_scenarios; +use l10nregistry::testing::TestFileFetcher; + +use unic_langid::LanguageIdentifier; + +fn preferences_bench(c: &mut Criterion) { + let fetcher = TestFileFetcher::new(); + + let mut group = c.benchmark_group("registry/scenarios"); + + for scenario in get_scenarios() { + let res_ids = scenario.res_ids.clone(); + + let locales: Vec = scenario + .locales + .iter() + .map(|l| l.parse().unwrap()) + .collect(); + + group.bench_function(format!("{}/sync/first_bundle", scenario.name), |b| { + b.iter(|| { + let reg = fetcher.get_registry(&scenario); + let mut bundles = + reg.generate_bundles_sync(locales.clone().into_iter(), res_ids.clone()); + assert!(bundles.next().is_some()); + }) + }); + + #[cfg(feature = "tokio")] + { + use futures::stream::StreamExt; + + let rt = tokio::runtime::Runtime::new().unwrap(); + + group.bench_function(&format!("{}/async/first_bundle", scenario.name), |b| { + b.iter(|| { + rt.block_on(async { + let reg = fetcher.get_registry(&scenario); + + let mut bundles = + reg.generate_bundles(locales.clone().into_iter(), res_ids.clone()); + assert!(bundles.next().await.is_some()); + }); + }) + }); + } + } + + group.finish(); +} + +criterion_group!(benches, preferences_bench); +criterion_main!(benches); diff --git a/third_party/rust/l10nregistry/benches/solver.rs b/third_party/rust/l10nregistry/benches/solver.rs new file mode 100644 index 000000000000..25906fa7c0af --- /dev/null +++ b/third_party/rust/l10nregistry/benches/solver.rs @@ -0,0 +1,120 @@ +use criterion::criterion_group; +use criterion::criterion_main; +use criterion::Criterion; + +use futures::stream::Collect; +use futures::stream::FuturesOrdered; +use futures::StreamExt; +use l10nregistry::solver::testing::get_scenarios; +use l10nregistry::solver::{AsyncTester, ParallelProblemSolver, SerialProblemSolver, SyncTester}; +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; + +pub struct MockTester { + values: Vec>, +} + +impl SyncTester for MockTester { + fn test_sync(&self, res_idx: usize, source_idx: usize) -> bool { + self.values[res_idx][source_idx] + } +} + +pub struct SingleTestResult(bool); + +impl Future for SingleTestResult { + type Output = bool; + + fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { + self.0.into() + } +} + +pub type ResourceSetStream = Collect, Vec>; +pub struct TestResult(ResourceSetStream); + +impl std::marker::Unpin for TestResult {} + +impl Future for TestResult { + type Output = Vec; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let pinned = Pin::new(&mut self.0); + pinned.poll(cx) + } +} + +impl AsyncTester for MockTester { + type Result = TestResult; + + fn test_async(&self, query: Vec<(usize, usize)>) -> Self::Result { + let futures = query + .into_iter() + .map(|(res_idx, source_idx)| SingleTestResult(self.test_sync(res_idx, source_idx))) + .collect::>(); + TestResult(futures.into_iter().collect::>().collect()) + } +} + +struct TestStream<'t> { + solver: ParallelProblemSolver, + tester: &'t MockTester, +} + +impl<'t> TestStream<'t> { + pub fn new(solver: ParallelProblemSolver, tester: &'t MockTester) -> Self { + Self { solver, tester } + } +} + +impl<'t> futures::stream::Stream for TestStream<'t> { + type Item = Vec; + + fn poll_next( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + let tester = self.tester; + let solver = &mut self.solver; + let pinned = std::pin::Pin::new(solver); + pinned + .try_poll_next(cx, tester, false) + .map(|v| v.ok().flatten()) + } +} + +fn solver_bench(c: &mut Criterion) { + let scenarios = get_scenarios(); + + let mut group = c.benchmark_group("solver"); + + for scenario in scenarios { + let tester = MockTester { + values: scenario.values.clone(), + }; + + group.bench_function(&format!("serial/{}", &scenario.name), |b| { + b.iter(|| { + let mut gen = SerialProblemSolver::new(scenario.width, scenario.depth); + while let Ok(Some(_)) = gen.try_next(&tester, false) {} + }) + }); + + { + let rt = tokio::runtime::Runtime::new().unwrap(); + + group.bench_function(&format!("parallel/{}", &scenario.name), |b| { + b.iter(|| { + let gen = ParallelProblemSolver::new(scenario.width, scenario.depth); + let mut t = TestStream::new(gen, &tester); + rt.block_on(async { while let Some(_) = t.next().await {} }); + }) + }); + } + } + group.finish(); +} + +criterion_group!(benches, solver_bench); +criterion_main!(benches); diff --git a/third_party/rust/l10nregistry/benches/source.rs b/third_party/rust/l10nregistry/benches/source.rs new file mode 100644 index 000000000000..b6bc119aa6da --- /dev/null +++ b/third_party/rust/l10nregistry/benches/source.rs @@ -0,0 +1,58 @@ +use criterion::criterion_group; +use criterion::criterion_main; +use criterion::Criterion; + +use fluent_testing::get_scenarios; +use l10nregistry::testing::TestFileFetcher; + +use unic_langid::LanguageIdentifier; + +fn get_locales(input: &[S]) -> Vec +where + S: AsRef, +{ + input.iter().map(|s| s.as_ref().parse().unwrap()).collect() +} + +fn source_bench(c: &mut Criterion) { + let fetcher = TestFileFetcher::new(); + + let mut group = c.benchmark_group("source/scenarios"); + + for scenario in get_scenarios() { + let res_ids = scenario.res_ids.clone(); + + let locales: Vec = get_locales(&scenario.locales); + + let sources: Vec<_> = scenario + .file_sources + .iter() + .map(|s| fetcher.get_test_file_source(&s.name, get_locales(&s.locales), &s.path_scheme)) + .collect(); + + group.bench_function(format!("{}/has_file", scenario.name), |b| { + b.iter(|| { + for source in &sources { + for res_id in &res_ids { + source.has_file(&locales[0], &res_id); + } + } + }) + }); + + group.bench_function(format!("{}/sync/fetch_file_sync", scenario.name), |b| { + b.iter(|| { + for source in &sources { + for res_id in &res_ids { + source.fetch_file_sync(&locales[0], &res_id, false); + } + } + }) + }); + } + + group.finish(); +} + +criterion_group!(benches, source_bench); +criterion_main!(benches); diff --git a/third_party/rust/l10nregistry/src/env.rs b/third_party/rust/l10nregistry/src/env.rs new file mode 100644 index 000000000000..7cd1ff30f4ec --- /dev/null +++ b/third_party/rust/l10nregistry/src/env.rs @@ -0,0 +1,5 @@ +use crate::errors::L10nRegistryError; + +pub trait ErrorReporter { + fn report_errors(&self, errors: Vec); +} diff --git a/third_party/rust/l10nregistry/src/errors.rs b/third_party/rust/l10nregistry/src/errors.rs new file mode 100644 index 000000000000..b81c55cf3871 --- /dev/null +++ b/third_party/rust/l10nregistry/src/errors.rs @@ -0,0 +1,62 @@ +use fluent_bundle::FluentError; +use std::error::Error; +use unic_langid::LanguageIdentifier; + +#[derive(Debug, Clone, PartialEq)] +pub enum L10nRegistryError { + FluentError { + path: String, + loc: Option<(usize, usize)>, + error: FluentError, + }, + MissingResource { + locale: LanguageIdentifier, + res_id: String, + }, +} + +impl std::fmt::Display for L10nRegistryError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::MissingResource { locale, res_id } => { + write!(f, "Missing resource in locale {}: {}", locale, res_id) + } + Self::FluentError { path, loc, error } => { + if let Some(loc) = loc { + write!( + f, + "Fluent Error in {}[line: {}, col: {}]: {}", + path, loc.0, loc.1, error + ) + } else { + write!(f, "Fluent Error in {}: {}", path, error) + } + } + } + } +} + +impl Error for L10nRegistryError {} + +#[derive(Debug, Clone, PartialEq)] +pub enum L10nRegistrySetupError { + RegistryLocked, + DuplicatedSource { name: String }, + MissingSource { name: String }, +} + +impl std::fmt::Display for L10nRegistrySetupError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::RegistryLocked => write!(f, "Can't modify a registry when locked."), + Self::DuplicatedSource { name } => { + write!(f, "Source with a name {} is already registered.", &name) + } + Self::MissingSource { name } => { + write!(f, "Cannot find a source with a name {}.", &name) + } + } + } +} + +impl Error for L10nRegistrySetupError {} diff --git a/third_party/rust/l10nregistry/src/fluent.rs b/third_party/rust/l10nregistry/src/fluent.rs new file mode 100644 index 000000000000..b6ac2a12ab46 --- /dev/null +++ b/third_party/rust/l10nregistry/src/fluent.rs @@ -0,0 +1,5 @@ +use fluent_bundle::FluentBundle as FluentBundleBase; +pub use fluent_bundle::{FluentError, FluentResource}; +use std::rc::Rc; + +pub type FluentBundle = FluentBundleBase>; diff --git a/third_party/rust/l10nregistry/src/lib.rs b/third_party/rust/l10nregistry/src/lib.rs new file mode 100644 index 000000000000..e28f04e5e8ee --- /dev/null +++ b/third_party/rust/l10nregistry/src/lib.rs @@ -0,0 +1,8 @@ +pub mod env; +pub mod errors; +pub mod fluent; +pub mod registry; +pub mod solver; +pub mod source; +#[cfg(feature = "fluent-testing")] +pub mod testing; diff --git a/third_party/rust/l10nregistry/src/registry/asynchronous.rs b/third_party/rust/l10nregistry/src/registry/asynchronous.rs new file mode 100644 index 000000000000..f1592479a176 --- /dev/null +++ b/third_party/rust/l10nregistry/src/registry/asynchronous.rs @@ -0,0 +1,207 @@ +use std::{ + pin::Pin, + task::{Context, Poll}, +}; + +use super::{BundleAdapter, L10nRegistry, L10nRegistryLocked}; +use crate::solver::{AsyncTester, ParallelProblemSolver}; +use crate::{ + env::ErrorReporter, + errors::L10nRegistryError, + fluent::{FluentBundle, FluentError}, + source::{ResourceOption, ResourceStatus}, +}; + +use fluent_fallback::generator::BundleStream; +use futures::{ + stream::{Collect, FuturesOrdered}, + Stream, StreamExt, +}; +use std::future::Future; +use unic_langid::LanguageIdentifier; + +impl<'a, B> L10nRegistryLocked<'a, B> {} + +impl L10nRegistry +where + P: Clone, + B: Clone, +{ + pub fn generate_bundles_for_lang( + &self, + langid: LanguageIdentifier, + resource_ids: Vec, + ) -> GenerateBundles { + let lang_ids = vec![langid]; + + GenerateBundles::new(self.clone(), lang_ids.into_iter(), resource_ids) + } + + pub fn generate_bundles( + &self, + locales: std::vec::IntoIter, + resource_ids: Vec, + ) -> GenerateBundles { + GenerateBundles::new(self.clone(), locales, resource_ids) + } +} + +enum State { + Empty, + Locale(LanguageIdentifier), + Solver { + locale: LanguageIdentifier, + solver: ParallelProblemSolver>, + }, +} + +impl Default for State { + fn default() -> Self { + Self::Empty + } +} + +impl State { + fn get_locale(&self) -> &LanguageIdentifier { + match self { + Self::Locale(locale) => locale, + Self::Solver { locale, .. } => locale, + Self::Empty => unreachable!(), + } + } + + fn take_solver(&mut self) -> ParallelProblemSolver> { + replace_with::replace_with_or_default_and_return(self, |self_| match self_ { + Self::Solver { locale, solver } => (solver, Self::Locale(locale)), + _ => unreachable!(), + }) + } + + fn put_back_solver(&mut self, solver: ParallelProblemSolver>) { + replace_with::replace_with_or_default(self, |self_| match self_ { + Self::Locale(locale) => Self::Solver { locale, solver }, + _ => unreachable!(), + }) + } +} + +pub struct GenerateBundles { + reg: L10nRegistry, + locales: std::vec::IntoIter, + res_ids: Vec, + state: State, +} + +impl GenerateBundles { + fn new( + reg: L10nRegistry, + locales: std::vec::IntoIter, + res_ids: Vec, + ) -> Self { + Self { + reg, + locales, + res_ids, + state: State::Empty, + } + } +} + +pub type ResourceSetStream = Collect, Vec>; +pub struct TestResult(ResourceSetStream); +impl std::marker::Unpin for TestResult {} + +impl Future for TestResult { + type Output = Vec; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let pinned = Pin::new(&mut self.0); + pinned + .poll(cx) + .map(|set| set.iter().map(|c| c.is_some()).collect()) + } +} + +impl<'l, P, B> AsyncTester for GenerateBundles { + type Result = TestResult; + + fn test_async(&self, query: Vec<(usize, usize)>) -> Self::Result { + let locale = self.state.get_locale(); + let lock = self.reg.lock(); + + let stream = query + .iter() + .map(|(res_idx, source_idx)| { + let res = &self.res_ids[*res_idx]; + lock.source_idx(*source_idx).fetch_file(locale, res) + }) + .collect::>(); + TestResult(stream.collect()) + } +} + +#[async_trait::async_trait(?Send)] +impl BundleStream for GenerateBundles { + async fn prefetch_async(&mut self) { + todo!(); + } +} + +impl Stream for GenerateBundles +where + P: ErrorReporter, + B: BundleAdapter, +{ + type Item = Result)>; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + loop { + if let State::Solver { .. } = self.state { + let mut solver = self.state.take_solver(); + let pinned_solver = Pin::new(&mut solver); + match pinned_solver.try_poll_next(cx, &self, false) { + std::task::Poll::Ready(order) => match order { + Ok(Some(order)) => { + let locale = self.state.get_locale(); + let bundle = self.reg.lock().bundle_from_order( + locale.clone(), + &order, + &self.res_ids, + &self.reg.shared.provider, + ); + self.state.put_back_solver(solver); + if bundle.is_some() { + return bundle.into(); + } else { + continue; + } + } + Ok(None) => { + self.state = State::Empty; + continue; + } + Err(idx) => { + self.reg.shared.provider.report_errors(vec![ + L10nRegistryError::MissingResource { + locale: self.state.get_locale().clone(), + res_id: self.res_ids[idx].clone(), + }, + ]); + self.state = State::Empty; + continue; + } + }, + std::task::Poll::Pending => { + self.state.put_back_solver(solver); + return std::task::Poll::Pending; + } + } + } else if let Some(locale) = self.locales.next() { + let solver = ParallelProblemSolver::new(self.res_ids.len(), self.reg.lock().len()); + self.state = State::Solver { locale, solver }; + } else { + return None.into(); + } + } + } +} diff --git a/third_party/rust/l10nregistry/src/registry/mod.rs b/third_party/rust/l10nregistry/src/registry/mod.rs new file mode 100644 index 000000000000..fdb45865374f --- /dev/null +++ b/third_party/rust/l10nregistry/src/registry/mod.rs @@ -0,0 +1,236 @@ +mod asynchronous; +mod synchronous; + +use std::{ + cell::{Ref, RefCell}, + collections::HashSet, + rc::Rc, +}; + +use crate::errors::L10nRegistrySetupError; +use crate::source::FileSource; + +use crate::env::ErrorReporter; +use crate::fluent::FluentBundle; +use fluent_bundle::FluentResource; +use fluent_fallback::generator::BundleGenerator; +use unic_langid::LanguageIdentifier; + +pub use asynchronous::GenerateBundles; +pub use synchronous::GenerateBundlesSync; + +pub type FluentResourceSet = Vec>; + +#[derive(Default)] +struct Shared { + sources: RefCell>, + provider: P, + bundle_adapter: Option, +} + +pub struct L10nRegistryLocked<'a, B> { + lock: Ref<'a, Vec>, + bundle_adapter: Option<&'a B>, +} + +impl<'a, B> L10nRegistryLocked<'a, B> { + pub fn iter(&self) -> impl Iterator { + self.lock.iter() + } + + pub fn len(&self) -> usize { + self.lock.len() + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn source_idx(&self, index: usize) -> &FileSource { + let source_idx = self.len() - 1 - index; + self.lock.get(source_idx).expect("Index out-of-range") + } + + pub fn get_source(&self, name: &str) -> Option<&FileSource> { + self.lock.iter().find(|&source| source.name == name) + } + + pub fn generate_sources_for_file<'l>( + &'l self, + langid: &'l LanguageIdentifier, + res_id: &'l str, + ) -> impl Iterator { + self.iter() + .filter(move |source| source.has_file(langid, res_id) != Some(false)) + } +} + +pub trait BundleAdapter { + fn adapt_bundle(&self, bundle: &mut FluentBundle); +} + +#[derive(Clone)] +pub struct L10nRegistry { + shared: Rc>, +} + +impl L10nRegistry { + pub fn with_provider(provider: P) -> Self { + Self { + shared: Rc::new(Shared { + sources: Default::default(), + provider, + bundle_adapter: None, + }), + } + } + + pub fn set_adapt_bundle(&mut self, bundle_adapter: B) -> Result<(), L10nRegistrySetupError> + where + B: BundleAdapter, + { + let shared = Rc::get_mut(&mut self.shared).ok_or(L10nRegistrySetupError::RegistryLocked)?; + shared.bundle_adapter = Some(bundle_adapter); + Ok(()) + } + + pub fn lock(&self) -> L10nRegistryLocked<'_, B> { + L10nRegistryLocked { + lock: self.shared.sources.borrow(), + bundle_adapter: self.shared.bundle_adapter.as_ref(), + } + } + + pub fn register_sources( + &self, + new_sources: Vec, + ) -> Result<(), L10nRegistrySetupError> { + let mut sources = self + .shared + .sources + .try_borrow_mut() + .map_err(|_| L10nRegistrySetupError::RegistryLocked)?; + + for new_source in new_sources { + if sources.iter().any(|source| source == &new_source) { + return Err(L10nRegistrySetupError::DuplicatedSource { + name: new_source.name, + }); + } + sources.push(new_source); + } + Ok(()) + } + + pub fn update_sources( + &self, + upd_sources: Vec, + ) -> Result<(), L10nRegistrySetupError> { + let mut sources = self + .shared + .sources + .try_borrow_mut() + .map_err(|_| L10nRegistrySetupError::RegistryLocked)?; + + for upd_source in upd_sources { + if let Some(idx) = sources.iter().position(|source| *source == upd_source) { + *sources.get_mut(idx).unwrap() = upd_source; + } else { + return Err(L10nRegistrySetupError::MissingSource { + name: upd_source.name, + }); + } + } + Ok(()) + } + + pub fn remove_sources(&self, del_sources: Vec) -> Result<(), L10nRegistrySetupError> + where + S: ToString, + { + let mut sources = self + .shared + .sources + .try_borrow_mut() + .map_err(|_| L10nRegistrySetupError::RegistryLocked)?; + let del_sources: Vec = del_sources.into_iter().map(|s| s.to_string()).collect(); + + sources.retain(|source| !del_sources.contains(&source.name)); + Ok(()) + } + + pub fn clear_sources(&self) -> Result<(), L10nRegistrySetupError> { + let mut sources = self + .shared + .sources + .try_borrow_mut() + .map_err(|_| L10nRegistrySetupError::RegistryLocked)?; + sources.clear(); + Ok(()) + } + + pub fn get_source_names(&self) -> Result, L10nRegistrySetupError> { + let sources = self + .shared + .sources + .try_borrow_mut() + .map_err(|_| L10nRegistrySetupError::RegistryLocked)?; + Ok(sources.iter().map(|s| s.name.clone()).collect()) + } + + pub fn has_source(&self, name: &str) -> Result { + let sources = self + .shared + .sources + .try_borrow_mut() + .map_err(|_| L10nRegistrySetupError::RegistryLocked)?; + Ok(sources.iter().any(|source| source.name == name)) + } + + pub fn get_source(&self, name: &str) -> Result, L10nRegistrySetupError> { + let sources = self + .shared + .sources + .try_borrow_mut() + .map_err(|_| L10nRegistrySetupError::RegistryLocked)?; + Ok(sources.iter().find(|source| source.name == name).cloned()) + } + + pub fn get_available_locales(&self) -> Result, L10nRegistrySetupError> { + let sources = self + .shared + .sources + .try_borrow_mut() + .map_err(|_| L10nRegistrySetupError::RegistryLocked)?; + let mut result = HashSet::new(); + for source in sources.iter() { + for locale in source.locales() { + result.insert(locale); + } + } + Ok(result.into_iter().map(|l| l.to_owned()).collect()) + } +} + +impl BundleGenerator for L10nRegistry +where + P: ErrorReporter + Clone, + B: BundleAdapter + Clone, +{ + type Resource = Rc; + type Iter = GenerateBundlesSync; + type Stream = GenerateBundles; + type LocalesIter = std::vec::IntoIter; + + fn bundles_iter(&self, locales: Self::LocalesIter, resource_ids: Vec) -> Self::Iter { + self.generate_bundles_sync(locales, resource_ids) + } + + fn bundles_stream( + &self, + locales: Self::LocalesIter, + resource_ids: Vec, + ) -> Self::Stream { + self.generate_bundles(locales, resource_ids) + } +} diff --git a/third_party/rust/l10nregistry/src/registry/synchronous.rs b/third_party/rust/l10nregistry/src/registry/synchronous.rs new file mode 100644 index 000000000000..8a02591c7f1d --- /dev/null +++ b/third_party/rust/l10nregistry/src/registry/synchronous.rs @@ -0,0 +1,235 @@ +use super::{BundleAdapter, L10nRegistry, L10nRegistryLocked}; +use crate::env::ErrorReporter; +use crate::errors::L10nRegistryError; +use crate::fluent::{FluentBundle, FluentError}; +use crate::solver::{SerialProblemSolver, SyncTester}; +use fluent_fallback::generator::BundleIterator; + +use unic_langid::LanguageIdentifier; + +impl<'a, B> L10nRegistryLocked<'a, B> { + pub(crate) fn bundle_from_order

( + &self, + locale: LanguageIdentifier, + source_order: &[usize], + res_ids: &[String], + error_reporter: &P, + ) -> Option)>> + where + P: ErrorReporter, + B: BundleAdapter, + { + let mut bundle = FluentBundle::new(vec![locale.clone()]); + + if let Some(bundle_adapter) = self.bundle_adapter { + bundle_adapter.adapt_bundle(&mut bundle); + } + + let mut errors = vec![]; + + for (&source_idx, path) in source_order.iter().zip(res_ids.iter()) { + let source = self.source_idx(source_idx); + if let Some(res) = source.fetch_file_sync(&locale, path, false) { + if source.options.allow_override { + bundle.add_resource_overriding(res); + } else if let Err(err) = bundle.add_resource(res) { + errors.extend(err.into_iter().map(|error| { + L10nRegistryError::FluentError { + path: path.clone(), + loc: None, + error, + } + })); + } + } else { + return None; + } + } + + if !errors.is_empty() { + error_reporter.report_errors(errors); + } + Some(Ok(bundle)) + } +} + +impl L10nRegistry +where + P: Clone, + B: Clone, +{ + pub fn generate_bundles_for_lang_sync( + &self, + langid: LanguageIdentifier, + resource_ids: Vec, + ) -> GenerateBundlesSync { + let lang_ids = vec![langid]; + + GenerateBundlesSync::new(self.clone(), lang_ids.into_iter(), resource_ids) + } + + pub fn generate_bundles_sync( + &self, + locales: std::vec::IntoIter, + resource_ids: Vec, + ) -> GenerateBundlesSync { + GenerateBundlesSync::new(self.clone(), locales, resource_ids) + } +} + +enum State { + Empty, + Locale(LanguageIdentifier), + Solver { + locale: LanguageIdentifier, + solver: SerialProblemSolver, + }, +} + +impl Default for State { + fn default() -> Self { + Self::Empty + } +} + +impl State { + fn get_locale(&self) -> &LanguageIdentifier { + match self { + Self::Locale(locale) => locale, + Self::Solver { locale, .. } => locale, + Self::Empty => unreachable!(), + } + } + + fn take_solver(&mut self) -> SerialProblemSolver { + replace_with::replace_with_or_default_and_return(self, |self_| match self_ { + Self::Solver { locale, solver } => (solver, Self::Locale(locale)), + _ => unreachable!(), + }) + } + + fn put_back_solver(&mut self, solver: SerialProblemSolver) { + replace_with::replace_with_or_default(self, |self_| match self_ { + Self::Locale(locale) => Self::Solver { locale, solver }, + _ => unreachable!(), + }) + } +} + +pub struct GenerateBundlesSync { + reg: L10nRegistry, + locales: std::vec::IntoIter, + res_ids: Vec, + state: State, +} + +impl GenerateBundlesSync { + fn new( + reg: L10nRegistry, + locales: std::vec::IntoIter, + res_ids: Vec, + ) -> Self { + Self { + reg, + locales, + res_ids, + state: State::Empty, + } + } +} + +impl SyncTester for GenerateBundlesSync { + fn test_sync(&self, res_idx: usize, source_idx: usize) -> bool { + let locale = self.state.get_locale(); + let res = &self.res_ids[res_idx]; + self.reg + .lock() + .source_idx(source_idx) + .fetch_file_sync(locale, res, false) + .is_some() + } +} + +impl BundleIterator for GenerateBundlesSync +where + P: ErrorReporter, +{ + fn prefetch_sync(&mut self) { + if let State::Solver { .. } = self.state { + let mut solver = self.state.take_solver(); + if let Err(idx) = solver.try_next(self, true) { + self.reg + .shared + .provider + .report_errors(vec![L10nRegistryError::MissingResource { + locale: self.state.get_locale().clone(), + res_id: self.res_ids[idx].clone(), + }]); + } + self.state.put_back_solver(solver); + return; + } + + if let Some(locale) = self.locales.next() { + let mut solver = SerialProblemSolver::new(self.res_ids.len(), self.reg.lock().len()); + self.state = State::Locale(locale.clone()); + if let Err(idx) = solver.try_next(self, true) { + self.reg + .shared + .provider + .report_errors(vec![L10nRegistryError::MissingResource { + locale, + res_id: self.res_ids[idx].clone(), + }]); + } + self.state.put_back_solver(solver); + } + } +} + +impl Iterator for GenerateBundlesSync +where + P: ErrorReporter, + B: BundleAdapter, +{ + type Item = Result)>; + + fn next(&mut self) -> Option { + loop { + if let State::Solver { .. } = self.state { + let mut solver = self.state.take_solver(); + match solver.try_next(self, false) { + Ok(Some(order)) => { + let locale = self.state.get_locale(); + let bundle = self.reg.lock().bundle_from_order( + locale.clone(), + order, + &self.res_ids, + &self.reg.shared.provider, + ); + self.state.put_back_solver(solver); + if bundle.is_some() { + return bundle; + } else { + continue; + } + } + Ok(None) => {} + Err(idx) => { + self.reg.shared.provider.report_errors(vec![ + L10nRegistryError::MissingResource { + locale: self.state.get_locale().clone(), + res_id: self.res_ids[idx].clone(), + }, + ]); + } + } + self.state = State::Empty; + } + + let locale = self.locales.next()?; + let solver = SerialProblemSolver::new(self.res_ids.len(), self.reg.lock().len()); + self.state = State::Solver { locale, solver }; + } + } +} diff --git a/third_party/rust/l10nregistry/src/solver/README.md b/third_party/rust/l10nregistry/src/solver/README.md new file mode 100644 index 000000000000..2315ea6fa372 --- /dev/null +++ b/third_party/rust/l10nregistry/src/solver/README.md @@ -0,0 +1,240 @@ + +Source Order Problem Solver +====================== + +This module contains an algorithm used to power the `FluentBundle` generator in `L10nRegistry`. + +The main concept behind it is a problem solver which takes a list of resources and a list of sources and computes all possible iterations of valid combinations of source orders that allow for creation of `FluentBundle` with the requested resources. + +The algorithm is notoriously hard to read, write, and modify, which prompts this documentation to be extensive and provide an example with diagram presentations to aid the reader. + +# Example +For the purpose of a graphical illustration of the example, we will evaluate a scenario with two sources and three resources. + +The sources and resource identifiers will be named in concise way (*1* or *A*) to simplify diagrams, while a more tangible names derived from real-world examples in Firefox use-case will be listed in their initial definition. + +### Sources +A source can be a packaged directory, and a language pack, or any other directory, zip file, or remote source which contains localization resource files. +In the example, we have two sources: +* Source 1 named ***0*** (e.g. `browser`) +* Source 2 named ***1*** (e.g. `toolkit`) + +### Resources +A resource is a single Fluent Translation List file. `FluentBundle` is a combination of such resources used together to resolve translations. This algorithm operates on lists of resource identifiers which represent relative paths within the source. +In the example we have three resources: +* Resource 1 named ***A*** (e.g. `branding/brand.ftl`) +* Resource 2 named ***B*** (e.g. `errors/common.ftl`) +* Resource 3 named ***C*** (e.g. `menu/list.ftl`) + +## Task +The task in this example is to generate all possible iterations of the three resources from the given two sources. Since I/O is expensive, and in most production scenarios all necessary translations are available in the first set, the iterator is used to lazily fallback on the alternative sets only in case of missing translations. + +If all resources are available in both sources, the iterator should produce the following results: +1. `[A0, B0, C0]` +2. `[A0, B0, C1]` +3. `[A0, B1, C0]` +4. `[A0, B1, C1]` +5. `[A1, B0, C0]` +6. `[A1, B0, C1]` +7. `[A1, B1, C0]` +8. `[A1, B1, C1]` + +Since the resources are defined by their column, we can store the resources as `[A, B, C]` separately and simplify the notation to just: +1. `[0, 0, 0]` +2. `[0, 0, 1]` +3. `[0, 1, 0]` +4. `[0, 1, 1]` +5. `[1, 0, 0]` +6. `[1, 0, 1]` +7. `[1, 1, 0]` +8. `[1, 1, 1]` + +This notation will be used from now on. + +## State + +For the in-detail diagrams on the algorithm, we'll use another way to look at the iterator - by evaluating it state. At every point of the algorithm, there is a *partial solution* which may lead to a *complete solution*. It is encoded as: + +```rust +struct Solution { + candidate: Vec, + idx: usize, +} +``` + +and which starting point can be visualized as: + +```text + ▼ +┌┲━┱┬───┬───┐ +│┃0┃│ │ │ +└╂─╂┴───┴───┘ + ┃ ┃ + ┗━┛ +``` +###### Diagrams generated with use of http://marklodato.github.io/js-boxdrawing/ + +where the horizontal block is a candidate, vertical block is a set of sources possible for each resource, and the arrow represents the index of a resource the iterator is currently evaluating. + +With those tools introduced, we can now guide the reader through how the algorithm works. +But before we do that, it is important to justify writing a custom algorithm in place of existing generic solutions, and explain the two testing strategies which heavily impact the algorithm. + +# Existing libraries +Intuitively, the starting point to exploration of the problem scope would be to look at it as some variation of the [Cartesian Product](https://en.wikipedia.org/wiki/Cartesian_product) iterator. + +#### Python + +In Python, `itertools` package provides a function [`itertools::product`](https://docs.python.org/3/library/itertools.html#itertools.product) which can be used to generate such iterator: +```python +import itertools + +for set in itertools.product(range(2), repeat=3): + print(set) +``` + +#### Rust + +In Rust, crate [`itertools`](https://crates.io/crates/itertools) provides, [`multi_cartesian_product`](https://docs.rs/itertools/0.9.0/itertools/trait.Itertools.html#method.multi_cartesian_product) which can be used like this: +```rust +use itertools::Itertools; + +let multi_prod = (0..3).map(|i| 0..2) + .multi_cartesian_product(); + +for set in multi_prod { + println!("{:?}", set); +} +``` +([playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=6ef231f6b011b234babb0aa3e68b78ab)) + +#### Reasons for a custom algorithm + +Unfortunately, the computational complexity of generating all possible sets is growing exponentially, both in the cost of CPU and memory use. +On a high-end laptop, computing the sets for all possible variations of the example above generates *8* sets and takes only *700 nanoseconds*, but computing the same for four sources and 16 resources (a scenario theoretically possible in Firefox with one language pack and Preferences UI for example) generates over *4 billion* sets and takes over *2 minutes*. + +Since one part of static cost is the I/O, the application of a [Memoization](https://en.wikipedia.org/wiki/Memoization) technique allows us to minimize the cost of constructing, storing and retrieving sets. + +Second important observation is that in most scenarios any resource exists in only some of the sources, and ability to bail out from a branch of candidates that cannot lead to a solution yields significantly fewer permutations in result. + +## Optimizations + +The algorithm used here is highly efficient. For the conservative scenario listed above, where 4 sources and 15 resources are all present in every source, the total time on the reference hardware is cut from *2 minutes* to *24 seconds*, while generating the same *4 billion* sets for a **5x** performance improvement. + +### Streaming Iterator +Unline regular iterator, a streaming iterator allows a borrowed reference to be returned, which in this case, where the solver yields a read-only "view" of a solution, allows us to avoid having to clone it. + +### Cache +Memory is much less of a problem for the algorithm than CPU usage, so the solver uses a matrix of source/resource `Option` to memoize visited cells. This allows for each source/resource combination to be tested only once after which all future tests can be skipped. + +### Backtracking +This optimization allows to benefit from the recognition of the fact that most resources are only available in some sources. +Instead of generating all possible sets and then ignoring ones which are incomplete, it allows the algorithm to [backtrack](https://en.wikipedia.org/wiki/Backtracking) from partial candidates that cannot lead to a complete solution. + +That technique is very powerful in the `L10nRegistry` use case and in many scenarios leads to 10-100x speed ups even in cases where all sets have to be generated. + +# Serial vs Parallel Testing +At the core of the solver is a *tester* component which is responsible for eagerly evaluating candidates to allow for early bailouts from partial solutions which cannot lead to a complete solution. + +This can be performed in one of two ways: + +### Serial + The algorithm is synchronous and each extension of the candidate is evaluated serially, one by one, allowing the for *backtracking* as soon as a given extension of a partial solution is confirmed to not lead to a complete solution. + +Bringing back the initial state of the solver: + +```text + ▼ +┌┲━┱┬───┬───┐ +│┃0┃│ │ │ +└╂─╂┴───┴───┘ + ┃ ┃ + ┗━┛ +``` + +The tester will evaluate whether the first resource **A** is available in the first source **0**. The testing will be performed synchronously, and the result will inform the algorithm on whether the candidate may lead to a complete solution, or this branch should be bailed out from, and the next candidate must be tried. + +#### Success case + +If the test returns a success, the extensions of the candidate is generated: +```text + ▼ +┌┲━┱┬┲━┱┬───┐ +│┃0┃│┃0┃│ │ +└╂─╂┴╂─╂┴───┘ + ┃ ┃ ┃ ┃ + ┗━┛ ┗━┛ +``` + +When a candidate is complete, in other words, when the last cell of a candidate has been tested and did not lead to a backtrack, we know that the candidate is a solution to the problem, and we can yield it from the iterator. + +#### Failure case + +If the test returns a failure, the next step is to evaluate alternative source for the same resource. Let's assume that *Source 0* had *Resource A* but it does not have *Resource B*. In such case, the algorithm will increment the second cell's source index: + +```text + ▼ + ┏━┓ + ┃0┃ +┌┲━┱┬╂─╂┬───┐ +│┃0┃│┃1┃│ │ +└╂─╂┴┺━┹┴───┘ + ┃ ┃ + ┗━┛ + ``` + +and that will potentially lead to a partial solution `[0, 1, ]` to be stored for the next iteration. + +If the test fails and no more sources can be generated, the algorithm will *backtrack* from the current cell looking for a cell with the **highest** index prior to the cell that was being evaluated which is not yet on the last source. If such cell is found, the results of all cells **to the right** of the newfound cell are **erased** and the next branch can be evaluated. + +If no such cell can be found, that means that the iterator is complete. + +### Parallel + +If the testing can be performed in parallel, like an asynchronous I/O, the above *serial* solution is sub-optimal as it misses on the benefit of testing multiple cells at once. + +In such a scenario, the algorithm will construct a candidate that *can* be valid (bailing only from candidates that have been already memoized as unavailable), and then test all of the untested cells in that candidate at once. + +```text + ▼ +┌┲━┱┬┲━┱┬┲━┱┐ +│┃0┃│┃0┃│┃0┃│ +└╂─╂┴╂─╂┴╂─╂┘ + ┃ ┃ ┃ ┃ ┃ ┃ + ┗━┛ ┗━┛ ┗━┛ +``` + +When the parallel execution returns, the algorithm memoizes all new cell results and tests if the candidate is now a valid complete solution. + +#### Success case + +If the result a set of successes, the candidate is returned as a solution, and the algorithm proceeds to the same operation as if it was a failure. + +#### Failure case +If the result contains failures, the iterator will now backtrack to find the closest lower or equal cell to the current index which can be advanced to the next source. +In the example state above, the current cell can be advanced to *source 1* and then just a set of `[None, None, 1]` is to be evaluated by the tester (since we know that *A0* and *B0* are valid). + +If that is successful, the `[0, 0, 1]` set is a complete solution and is yielded. + +Then, if the iterator is resumed, the next state to be tested is: + +```text + ▼ + ┏━┓ + ┃0┃ +┌┲━┱┬╂─╂┬┲━┱┐ +│┃0┃│┃1┃│┃0┃│ +└╂─╂┴┺━┹┴╂─╂┘ + ┃ ┃ ┃ ┃ + ┗━┛ ┗━┛ +``` + +since cell *2* was at the highest index, cell *1* is the highest lower than *2* that was not at the highest source index position. That cell is advanced, and all cells after it are *pruned* (in this case, cell *2* is the only one). Then, the memoization kicks in, and since *A0* and *C0* are already cached as valid, the tester receives just `[None, 1, None]` to be tested and the algorithm continues. + +# Summary + +The algorithm explained above is tailored to the problem domain of `L10nRegistry` and is designed to be further extended in the future. + +It is important to maintain this guide up to date as any changes to the algorithm are to be made. + +Good luck. + diff --git a/third_party/rust/l10nregistry/src/solver/mod.rs b/third_party/rust/l10nregistry/src/solver/mod.rs new file mode 100644 index 000000000000..f14fbfe64127 --- /dev/null +++ b/third_party/rust/l10nregistry/src/solver/mod.rs @@ -0,0 +1,122 @@ +mod parallel; +mod serial; +pub mod testing; + +pub use parallel::{AsyncTester, ParallelProblemSolver}; +pub use serial::{SerialProblemSolver, SyncTester}; + +pub struct ProblemSolver { + width: usize, + depth: usize, + + cache: Vec>>, + + solution: Vec, + idx: usize, + + dirty: bool, +} + +impl ProblemSolver { + pub fn new(width: usize, depth: usize) -> Self { + Self { + width, + depth, + cache: vec![vec![None; depth]; width], + + solution: vec![0; width], + idx: 0, + + dirty: false, + } + } +} + +impl ProblemSolver { + pub fn bail(&mut self) -> bool { + if self.try_advance_source() { + true + } else { + self.try_backtrack() + } + } + + pub fn has_missing_cell(&self) -> Option { + for res_idx in 0..self.width { + if self.cache[res_idx].iter().all(|c| *c == Some(false)) { + return Some(res_idx); + } + } + None + } + + fn is_cell_missing(&self, res_idx: usize, source_idx: usize) -> bool { + if let Some(false) = self.cache[res_idx][source_idx] { + return true; + } + false + } + + fn is_current_cell_missing(&self) -> bool { + let res_idx = self.idx; + let source_idx = self.solution[res_idx]; + let cell = &self.cache[res_idx][source_idx]; + if let Some(false) = cell { + return true; + } + false + } + + pub fn try_advance_resource(&mut self) -> bool { + if self.idx >= self.width - 1 { + false + } else { + self.idx += 1; + while self.is_current_cell_missing() { + if !self.try_advance_source() { + return false; + } + } + true + } + } + + pub fn try_advance_source(&mut self) -> bool { + while self.solution[self.idx] < self.depth - 1 { + self.solution[self.idx] += 1; + if !self.is_current_cell_missing() { + return true; + } + } + false + } + + pub fn try_backtrack(&mut self) -> bool { + while self.solution[self.idx] == self.depth - 1 { + if self.idx == 0 { + return false; + } + self.idx -= 1; + } + self.solution[self.idx] += 1; + self.prune() + } + + pub fn prune(&mut self) -> bool { + for i in self.idx + 1..self.width { + let mut source_idx = 0; + while self.is_cell_missing(i, source_idx) { + if source_idx >= self.depth - 1 { + return false; + } + source_idx += 1; + } + self.solution[i] = source_idx; + } + true + } + + pub fn is_complete(&self) -> bool { + self.idx == self.width - 1 + } +} diff --git a/third_party/rust/l10nregistry/src/solver/parallel.rs b/third_party/rust/l10nregistry/src/solver/parallel.rs new file mode 100644 index 000000000000..5bdfa9d8db1c --- /dev/null +++ b/third_party/rust/l10nregistry/src/solver/parallel.rs @@ -0,0 +1,185 @@ +use super::ProblemSolver; +use std::ops::{Deref, DerefMut}; + +use futures::ready; +use std::future::Future; +use std::pin::Pin; + +pub trait AsyncTester { + type Result: Future>; + + fn test_async(&self, query: Vec<(usize, usize)>) -> Self::Result; +} + +pub struct ParallelProblemSolver +where + T: AsyncTester, +{ + solver: ProblemSolver, + current_test: Option<(T::Result, Vec)>, +} + +impl Deref for ParallelProblemSolver { + type Target = ProblemSolver; + + fn deref(&self) -> &Self::Target { + &self.solver + } +} + +impl DerefMut for ParallelProblemSolver { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.solver + } +} + +impl ParallelProblemSolver { + pub fn new(width: usize, depth: usize) -> Self { + Self { + solver: ProblemSolver::new(width, depth), + current_test: None, + } + } +} + +impl ParallelProblemSolver { + pub fn try_generate_complete_candidate(&mut self) -> bool { + while !self.is_complete() { + while self.is_current_cell_missing() { + if !self.try_advance_source() { + return false; + } + } + if !self.try_advance_resource() { + return false; + } + } + true + } + + fn try_generate_test_query(&mut self) -> Result<(Vec<(usize, usize)>, Vec), usize> { + let mut test_cells = vec![]; + let query = self + .solution + .iter() + .enumerate() + .filter_map(|(res_idx, source_idx)| { + let cell = self.cache[res_idx][*source_idx]; + match cell { + None => { + test_cells.push(res_idx); + Some(Ok((res_idx, *source_idx))) + } + Some(false) => Some(Err(res_idx)), + Some(true) => None, + } + }) + .collect::>()?; + Ok((query, test_cells)) + } + + fn apply_test_result( + &mut self, + resources: Vec, + testing_cells: Vec, + ) -> Result<(), usize> { + let mut first_missing = None; + for (result, res_idx) in resources.into_iter().zip(testing_cells) { + let source_idx = self.solution[res_idx]; + self.cache[res_idx][source_idx] = Some(result); + if !result && first_missing.is_none() { + first_missing = Some(res_idx); + } + } + if let Some(idx) = first_missing { + Err(idx) + } else { + Ok(()) + } + } + + pub fn try_poll_next( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + tester: &T, + prefetch: bool, + ) -> std::task::Poll>, usize>> + where + ::Result: Unpin, + { + if self.width == 0 || self.depth == 0 { + return Ok(None).into(); + } + + 'outer: loop { + if let Some((test, testing_cells)) = &mut self.current_test { + let pinned = Pin::new(test); + let set = ready!(pinned.poll(cx)); + let testing_cells = testing_cells.clone(); + + if let Err(res_idx) = self.apply_test_result(set, testing_cells) { + self.idx = res_idx; + self.prune(); + if !self.bail() { + if let Some(res_idx) = self.has_missing_cell() { + return Err(res_idx).into(); + } else { + return Ok(None).into(); + } + } + self.current_test = None; + continue 'outer; + } else { + self.current_test = None; + if !prefetch { + self.dirty = true; + } + return Ok(Some(self.solution.clone())).into(); + } + } else { + if self.dirty { + if !self.bail() { + if let Some(res_idx) = self.has_missing_cell() { + return Err(res_idx).into(); + } else { + return Ok(None).into(); + } + } + self.dirty = false; + } + while self.try_generate_complete_candidate() { + match self.try_generate_test_query() { + Ok((query, testing_cells)) => { + self.current_test = Some((tester.test_async(query), testing_cells)); + continue 'outer; + } + Err(res_idx) => { + self.idx = res_idx; + self.prune(); + if !self.bail() { + if let Some(res_idx) = self.has_missing_cell() { + return Err(res_idx).into(); + } else { + return Ok(None).into(); + } + } + } + } + } + return Ok(None).into(); + } + } + } +} + +#[cfg(test)] +mod tests { + #[test] + fn problem_solver() { + // let keys = vec!["key1.ftl", "key2.ftl"]; + // let sources = vec!["source1", "source2"]; + // let args = ("foo",); + + // let ps = ProblemSolver::new(keys.len(), sources.len(), &foo); + } +} diff --git a/third_party/rust/l10nregistry/src/solver/serial.rs b/third_party/rust/l10nregistry/src/solver/serial.rs new file mode 100644 index 000000000000..6b0f848e8f48 --- /dev/null +++ b/third_party/rust/l10nregistry/src/solver/serial.rs @@ -0,0 +1,88 @@ +use super::ProblemSolver; +use std::ops::{Deref, DerefMut}; + +pub trait SyncTester { + fn test_sync(&self, res_idx: usize, source_idx: usize) -> bool; +} + +pub struct SerialProblemSolver(ProblemSolver); + +impl Deref for SerialProblemSolver { + type Target = ProblemSolver; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for SerialProblemSolver { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl SerialProblemSolver { + pub fn new(width: usize, depth: usize) -> Self { + Self(ProblemSolver::new(width, depth)) + } +} + +impl SerialProblemSolver { + fn test_current_cell(&mut self, tester: &T) -> bool + where + T: SyncTester, + { + let res_idx = self.idx; + let source_idx = self.solution[res_idx]; + let cell = &mut self.cache[res_idx][source_idx]; + *cell.get_or_insert_with(|| tester.test_sync(res_idx, source_idx)) + } + + pub fn try_next(&mut self, tester: &T, prefetch: bool) -> Result, usize> + where + T: SyncTester, + { + if self.width == 0 || self.depth == 0 { + return Ok(None); + } + if self.dirty { + if !self.bail() { + return Ok(None); + } + self.dirty = false; + } + loop { + if !self.test_current_cell(tester) { + if !self.bail() { + if let Some(res_idx) = self.has_missing_cell() { + return Err(res_idx); + } else { + return Ok(None); + } + } + continue; + } + if self.is_complete() { + if !prefetch { + self.dirty = true; + } + return Ok(Some(&self.solution)); + } + if !self.try_advance_resource() { + return Ok(None); + } + } + } +} + +#[cfg(test)] +mod tests { + #[test] + fn problem_solver() { + // let keys = vec!["key1.ftl", "key2.ftl"]; + // let sources = vec!["source1", "source2"]; + // let args = ("foo",); + + // let ps = ProblemSolver::new(keys.len(), sources.len(), &foo); + } +} diff --git a/third_party/rust/l10nregistry/src/solver/testing/mod.rs b/third_party/rust/l10nregistry/src/solver/testing/mod.rs new file mode 100644 index 000000000000..f247d11c44c0 --- /dev/null +++ b/third_party/rust/l10nregistry/src/solver/testing/mod.rs @@ -0,0 +1,29 @@ +mod scenarios; + +pub use scenarios::get_scenarios; + +pub struct Scenario { + pub name: String, + pub width: usize, + pub depth: usize, + pub values: Vec>, + pub solutions: Vec>, +} + +impl Scenario { + pub fn new( + name: S, + width: usize, + depth: usize, + values: Vec>, + solutions: Vec>, + ) -> Self { + Self { + name: name.to_string(), + width, + depth, + values, + solutions, + } + } +} diff --git a/third_party/rust/l10nregistry/src/solver/testing/scenarios.rs b/third_party/rust/l10nregistry/src/solver/testing/scenarios.rs new file mode 100644 index 000000000000..8addec979bf5 --- /dev/null +++ b/third_party/rust/l10nregistry/src/solver/testing/scenarios.rs @@ -0,0 +1,151 @@ +use super::*; + +pub fn get_scenarios() -> Vec { + vec![ + Scenario::new("no-sources", 1, 0, vec![], vec![]), + Scenario::new("no-resources", 1, 0, vec![vec![true]], vec![]), + Scenario::new("no-keys", 0, 1, vec![], vec![]), + Scenario::new( + "one-res-two-sources", + 1, + 2, + vec![vec![true, true]], + vec![vec![0], vec![1]], + ), + Scenario::new( + "two-res-two-sources", + 2, + 2, + vec![vec![false, true], vec![true, false]], + vec![vec![1, 0]], + ), + Scenario::new( + "small", + 3, + 2, + vec![vec![true, true], vec![true, true], vec![true, true]], + vec![ + vec![0, 0, 0], + vec![0, 0, 1], + vec![0, 1, 0], + vec![0, 1, 1], + vec![1, 0, 0], + vec![1, 0, 1], + vec![1, 1, 0], + vec![1, 1, 1], + ], + ), + Scenario::new( + "incomplete", + 3, + 2, + vec![vec![true, false], vec![false, true], vec![true, true]], + vec![vec![0, 1, 0], vec![0, 1, 1]], + ), + Scenario::new( + "preferences", + 19, + 2, + vec![ + vec![true, false], + vec![true, false], + vec![true, false], + vec![true, false], + vec![true, false], + vec![true, false], + vec![true, false], + vec![true, false], + vec![true, false], + vec![true, false], + vec![true, false], + vec![true, false], + vec![true, false], + vec![true, false], + vec![true, false], + vec![true, false], + vec![false, true], + vec![false, true], + vec![false, true], + ], + vec![vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, + ]], + ), + Scenario::new( + "langpack", + 3, + 4, + vec![ + vec![true, true, true, true], + vec![true, true, true, true], + vec![true, true, true, true], + ], + vec![ + vec![0, 0, 0], + vec![0, 0, 1], + vec![0, 0, 2], + vec![0, 0, 3], + vec![0, 1, 0], + vec![0, 1, 1], + vec![0, 1, 2], + vec![0, 1, 3], + vec![0, 2, 0], + vec![0, 2, 1], + vec![0, 2, 2], + vec![0, 2, 3], + vec![0, 3, 0], + vec![0, 3, 1], + vec![0, 3, 2], + vec![0, 3, 3], + vec![1, 0, 0], + vec![1, 0, 1], + vec![1, 0, 2], + vec![1, 0, 3], + vec![1, 1, 0], + vec![1, 1, 1], + vec![1, 1, 2], + vec![1, 1, 3], + vec![1, 2, 0], + vec![1, 2, 1], + vec![1, 2, 2], + vec![1, 2, 3], + vec![1, 3, 0], + vec![1, 3, 1], + vec![1, 3, 2], + vec![1, 3, 3], + vec![2, 0, 0], + vec![2, 0, 1], + vec![2, 0, 2], + vec![2, 0, 3], + vec![2, 1, 0], + vec![2, 1, 1], + vec![2, 1, 2], + vec![2, 1, 3], + vec![2, 2, 0], + vec![2, 2, 1], + vec![2, 2, 2], + vec![2, 2, 3], + vec![2, 3, 0], + vec![2, 3, 1], + vec![2, 3, 2], + vec![2, 3, 3], + vec![3, 0, 0], + vec![3, 0, 1], + vec![3, 0, 2], + vec![3, 0, 3], + vec![3, 1, 0], + vec![3, 1, 1], + vec![3, 1, 2], + vec![3, 1, 3], + vec![3, 2, 0], + vec![3, 2, 1], + vec![3, 2, 2], + vec![3, 2, 3], + vec![3, 3, 0], + vec![3, 3, 1], + vec![3, 3, 2], + vec![3, 3, 3], + ], + ), + ] +} diff --git a/third_party/rust/l10nregistry/src/source/fetcher.rs b/third_party/rust/l10nregistry/src/source/fetcher.rs new file mode 100644 index 000000000000..0dcdaf36b240 --- /dev/null +++ b/third_party/rust/l10nregistry/src/source/fetcher.rs @@ -0,0 +1,29 @@ +use async_trait::async_trait; +use std::io; + +/// The users of [`FileSource`] implement this trait to provide loading of +/// resources, returning the contents of a resource as a +/// `String`. [`FileSource`] handles the conversion from string representation +/// into `FluentResource`. +/// +/// [`FileSource`]: source/struct.FileSource.html +#[async_trait(?Send)] +pub trait FileFetcher { + /// Return the `String` representation for `path`. This version is + /// blocking. + /// + /// See [`fetch`](#tymethod.fetch). + fn fetch_sync(&self, path: &str) -> io::Result; + + /// Return the `String` representation for `path`. + /// + /// On success, returns `Poll::Ready(Ok(..))`. + /// + /// If no resource is available to be fetched, the method returns + /// `Poll::Pending` and arranges for the current task (via + /// `cx.waker().wake_by_ref()`) to receive a notification when the resource + /// is available. + /// + /// See [`fetch_sync`](#tymethod.fetch_sync) + async fn fetch(&self, path: &str) -> io::Result; +} diff --git a/third_party/rust/l10nregistry/src/source/mod.rs b/third_party/rust/l10nregistry/src/source/mod.rs new file mode 100644 index 000000000000..9eb09aaed671 --- /dev/null +++ b/third_party/rust/l10nregistry/src/source/mod.rs @@ -0,0 +1,468 @@ +mod fetcher; +pub use fetcher::FileFetcher; + +use crate::env::ErrorReporter; +use crate::errors::L10nRegistryError; +use crate::fluent::FluentResource; + +use std::{ + borrow::Borrow, + cell::RefCell, + fmt, + hash::{Hash, Hasher}, + pin::Pin, + rc::Rc, + task::Poll, +}; + +use futures::{future::Shared, Future, FutureExt}; +use rustc_hash::FxHashMap; +use unic_langid::LanguageIdentifier; + +pub type RcResource = Rc; +pub type ResourceOption = Option; +pub type ResourceFuture = Shared>>>; + +#[derive(Debug, Clone)] +pub enum ResourceStatus { + /// The resource is missing. Don't bother trying to fetch. + Missing, + /// The resource is loading and future will deliver the result. + Loading(ResourceFuture), + /// The resource is loaded and parsed. + Loaded(RcResource), +} + +impl From for ResourceStatus { + fn from(input: ResourceOption) -> Self { + if let Some(res) = input { + Self::Loaded(res) + } else { + Self::Missing + } + } +} + +impl Future for ResourceStatus { + type Output = ResourceOption; + + fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { + use ResourceStatus::*; + + let this = &mut *self; + + match this { + Missing => None.into(), + Loaded(res) => Some(res.clone()).into(), + Loading(res) => Pin::new(res).poll(cx), + } + } +} + +/// `FileSource` provides a generic fetching and caching of fluent resources. +/// The user of `FileSource` provides a [`FileFetcher`](trait.FileFetcher.html) +/// implementation and `FileSource` takes care of the rest. +#[derive(Clone)] +pub struct FileSource { + pub name: String, + pub pre_path: String, + locales: Vec, + shared: Rc, + index: Option>, + pub options: FileSourceOptions, +} + +struct Inner { + fetcher: Box, + error_reporter: Option>>, + entries: RefCell>, +} + +impl fmt::Display for FileSource { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.name) + } +} + +impl PartialEq for FileSource { + fn eq(&self, other: &Self) -> bool { + self.name == other.name + } +} + +impl Eq for FileSource {} + +impl Hash for FileSource { + fn hash(&self, state: &mut H) { + self.name.hash(state) + } +} + +#[derive(PartialEq, Clone, Debug)] +pub struct FileSourceOptions { + pub allow_override: bool, +} + +impl Default for FileSourceOptions { + fn default() -> Self { + Self { + allow_override: false, + } + } +} + +impl FileSource { + /// Create a `FileSource` using the provided [`FileFetcher`](../trait.FileFetcher.html). + pub fn new( + name: String, + locales: Vec, + pre_path: String, + options: FileSourceOptions, + fetcher: impl FileFetcher + 'static, + ) -> Self { + FileSource { + name, + pre_path, + locales, + index: None, + shared: Rc::new(Inner { + entries: RefCell::new(FxHashMap::default()), + fetcher: Box::new(fetcher), + error_reporter: None, + }), + options, + } + } + + pub fn new_with_index( + name: String, + locales: Vec, + pre_path: String, + options: FileSourceOptions, + fetcher: impl FileFetcher + 'static, + index: Vec, + ) -> Self { + FileSource { + name, + pre_path, + locales, + index: Some(index), + shared: Rc::new(Inner { + entries: RefCell::new(FxHashMap::default()), + fetcher: Box::new(fetcher), + error_reporter: None, + }), + options, + } + } + + pub fn set_reporter(&mut self, reporter: impl ErrorReporter + 'static) { + let mut shared = Rc::get_mut(&mut self.shared).unwrap(); + shared.error_reporter = Some(RefCell::new(Box::new(reporter))); + } +} + +fn calculate_pos_in_source(source: &str, idx: usize) -> (usize, usize) { + let mut ptr = 0; + let mut result = (1, 1); + for line in source.lines() { + let bytes = line.as_bytes().len(); + if ptr + bytes < idx { + ptr += bytes + 1; + result.0 += 1; + } else { + result.1 = idx - ptr + 1; + break; + } + } + result +} + +impl FileSource { + fn get_path(&self, locale: &LanguageIdentifier, path: &str) -> String { + format!( + "{}{}", + self.pre_path.replace("{locale}", &locale.to_string()), + path + ) + } + + fn fetch_sync(&self, full_path: &str) -> ResourceOption { + self.shared + .fetcher + .fetch_sync(full_path) + .ok() + .map(|source| match FluentResource::try_new(source) { + Ok(res) => Rc::new(res), + Err((res, errors)) => { + if let Some(reporter) = &self.shared.error_reporter { + reporter.borrow().report_errors( + errors + .into_iter() + .map(|e| L10nRegistryError::FluentError { + path: full_path.to_string(), + loc: Some(calculate_pos_in_source(res.source(), e.pos.start)), + error: e.into(), + }) + .collect(), + ); + } + Rc::new(res) + } + }) + } + + /// Attempt to synchronously fetch resource for the combination of `locale` + /// and `path`. Returns `Some(ResourceResult)` if the resource is available, + /// else `None`. + pub fn fetch_file_sync( + &self, + locale: &LanguageIdentifier, + path: &str, + overload: bool, + ) -> ResourceOption { + use ResourceStatus::*; + + if self.has_file(locale, path) == Some(false) { + return None; + } + + let full_path = self.get_path(locale, &path); + + let res = self + .shared + .lookup_resource(full_path.clone(), || self.fetch_sync(&full_path).into()); + + match res { + Missing => None, + Loaded(res) => Some(res), + Loading(..) if overload => { + // A sync load has been requested for the same resource that has + // a pending async load in progress. How do we handle this? + // + // Ideally, we would sync load and resolve all the pending + // futures with the result. With the current Futures and + // combinators, it's unclear how to proceed. One potential + // solution is to store a oneshot::Sender and + // Shared. When the async loading future + // resolves it would check that the state is still `Loading`, + // and if so, send the result. The sync load would do the same + // send on the oneshot::Sender. + // + // For now, we warn and return the resource, paying the cost of + // duplication of the resource. + self.fetch_sync(&full_path) + } + Loading(..) => { + panic!("[l10nregistry] Attempting to synchronously load file {} while it's being loaded asynchronously.", &full_path); + } + } + } + + /// Attempt to fetch resource for the combination of `locale` and `path`. + /// Returns [`ResourceStatus`](enum.ResourceStatus.html) which is + /// a `Future` that can be polled. + pub fn fetch_file(&self, locale: &LanguageIdentifier, path: &str) -> ResourceStatus { + use ResourceStatus::*; + + if self.has_file(locale, path) == Some(false) { + return ResourceStatus::Missing; + } + + let full_path = self.get_path(locale, path); + + self.shared.lookup_resource(full_path.clone(), || { + let shared = self.shared.clone(); + Loading(read_resource(full_path, shared).boxed_local().shared()) + }) + } + + /// Determine if the `FileSource` has a loaded resource for the combination + /// of `locale` and `path`. Returns `Some(true)` if the file is loaded, else + /// `Some(false)`. `None` is returned if there is an outstanding async fetch + /// pending and the status is yet to be determined. + pub fn has_file>(&self, locale: L, path: &str) -> Option { + let locale = locale.borrow(); + if !self.locales.contains(locale) { + Some(false) + } else { + let full_path = self.get_path(locale, path); + if let Some(index) = &self.index { + return Some(index.iter().any(|p| p == &full_path)); + } + self.shared.has_file(&full_path) + } + } + + pub fn locales(&self) -> &[LanguageIdentifier] { + &self.locales + } + + pub fn get_index(&self) -> Option<&Vec> { + self.index.as_ref() + } +} + +impl std::fmt::Debug for FileSource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result { + if let Some(index) = &self.index { + f.debug_struct("FileSource") + .field("name", &self.name) + .field("locales", &self.locales) + .field("pre_path", &self.pre_path) + .field("index", index) + .finish() + } else { + f.debug_struct("FileSource") + .field("name", &self.name) + .field("locales", &self.locales) + .field("pre_path", &self.pre_path) + .finish() + } + } +} + +impl Inner { + fn lookup_resource(&self, path: String, f: F) -> ResourceStatus + where + F: FnOnce() -> ResourceStatus, + { + let mut lock = self.entries.borrow_mut(); + lock.entry(path).or_insert_with(|| f()).clone() + } + + fn update_resource(&self, path: String, resource: ResourceOption) -> ResourceOption { + let mut lock = self.entries.borrow_mut(); + let entry = lock.get_mut(&path); + match entry { + Some(entry) => *entry = resource.clone().into(), + _ => panic!("Expected "), + } + resource + } + + pub fn has_file(&self, full_path: &str) -> Option { + match self.entries.borrow().get(full_path) { + Some(ResourceStatus::Missing) => Some(false), + Some(ResourceStatus::Loaded(_)) => Some(true), + Some(ResourceStatus::Loading(_)) | None => None, + } + } +} + +async fn read_resource(path: String, shared: Rc) -> ResourceOption { + let resource = + shared.fetcher.fetch(&path).await.ok().map(|source| { + match FluentResource::try_new(source) { + Ok(res) => Rc::new(res), + Err((res, errors)) => { + if let Some(reporter) = &shared.error_reporter.borrow() { + reporter.borrow().report_errors( + errors + .into_iter() + .map(|e| L10nRegistryError::FluentError { + path: path.clone(), + loc: Some(calculate_pos_in_source(res.source(), e.pos.start)), + error: e.into(), + }) + .collect(), + ); + } + Rc::new(res) + } + } + }); + // insert the resource into the cache + shared.update_resource(path, resource) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn calculate_source_pos() { + let source = r#" +key = Value + +key2 = Value 2 +"# + .trim(); + let result = calculate_pos_in_source(source, 0); + assert_eq!(result, (1, 1)); + + let result = calculate_pos_in_source(source, 1); + assert_eq!(result, (1, 2)); + + let result = calculate_pos_in_source(source, 12); + assert_eq!(result, (2, 1)); + + let result = calculate_pos_in_source(source, 13); + assert_eq!(result, (3, 1)); + } +} + +#[cfg(test)] +#[cfg(feature = "tokio")] +mod tests_tokio { + use super::*; + use crate::testing::TestFileFetcher; + + const FTL_RESOURCE_PRESENT: &str = "toolkit/global/textActions.ftl"; + const FTL_RESOURCE_MISSING: &str = "missing.ftl"; + + #[tokio::test] + async fn file_source_fetch() { + let fetcher = TestFileFetcher::new(); + let en_us: LanguageIdentifier = "en-US".parse().unwrap(); + let fs1 = fetcher.get_test_file_source("toolkit", vec![en_us.clone()], "toolkit/{locale}/"); + + let file = fs1.fetch_file(&en_us, FTL_RESOURCE_PRESENT).await; + assert!(file.is_some()); + } + + #[tokio::test] + async fn file_source_fetch_missing() { + let fetcher = TestFileFetcher::new(); + let en_us: LanguageIdentifier = "en-US".parse().unwrap(); + let fs1 = fetcher.get_test_file_source("toolkit", vec![en_us.clone()], "toolkit/{locale}/"); + + let file = fs1.fetch_file(&en_us, FTL_RESOURCE_MISSING).await; + assert!(file.is_none()); + } + + #[tokio::test] + async fn file_source_already_loaded() { + let fetcher = TestFileFetcher::new(); + let en_us: LanguageIdentifier = "en-US".parse().unwrap(); + let fs1 = fetcher.get_test_file_source("toolkit", vec![en_us.clone()], "toolkit/{locale}/"); + + let file = fs1.fetch_file(&en_us, FTL_RESOURCE_PRESENT).await; + assert!(file.is_some()); + let file = fs1.fetch_file(&en_us, FTL_RESOURCE_PRESENT).await; + assert!(file.is_some()); + } + + #[tokio::test] + async fn file_source_concurrent() { + let fetcher = TestFileFetcher::new(); + let en_us: LanguageIdentifier = "en-US".parse().unwrap(); + let fs1 = fetcher.get_test_file_source("toolkit", vec![en_us.clone()], "toolkit/{locale}/"); + + let file1 = fs1.fetch_file(&en_us, FTL_RESOURCE_PRESENT); + let file2 = fs1.fetch_file(&en_us, FTL_RESOURCE_PRESENT); + assert!(file1.await.is_some()); + assert!(file2.await.is_some()); + } + + #[test] + fn file_source_sync_after_async_fail() { + let fetcher = TestFileFetcher::new(); + let en_us: LanguageIdentifier = "en-US".parse().unwrap(); + let fs1 = fetcher.get_test_file_source("toolkit", vec![en_us.clone()], "toolkit/{locale}/"); + + let _ = fs1.fetch_file(&en_us, FTL_RESOURCE_PRESENT); + let file2 = fs1.fetch_file_sync(&en_us, FTL_RESOURCE_PRESENT, true); + assert!(file2.is_some()); + } +} diff --git a/third_party/rust/l10nregistry/src/testing.rs b/third_party/rust/l10nregistry/src/testing.rs new file mode 100644 index 000000000000..8cb57d2146ec --- /dev/null +++ b/third_party/rust/l10nregistry/src/testing.rs @@ -0,0 +1,295 @@ +use crate::env::ErrorReporter; +use crate::errors::L10nRegistryError; +use crate::fluent::FluentBundle; +use crate::registry::BundleAdapter; +use crate::registry::L10nRegistry; +use crate::source::FileFetcher; +use async_trait::async_trait; +use fluent_fallback::env::LocalesProvider; +use fluent_testing::MockFileSystem; +use std::cell::RefCell; +use std::rc::Rc; +use unic_langid::LanguageIdentifier; + +pub struct RegistrySetup { + pub name: String, + pub file_sources: Vec, + pub locales: Vec, +} + +pub struct FileSource { + pub name: String, + pub locales: Vec, + pub path_scheme: String, +} + +#[derive(Clone)] +pub struct MockBundleAdapter; + +impl BundleAdapter for MockBundleAdapter { + fn adapt_bundle(&self, _bundle: &mut FluentBundle) {} +} + +impl FileSource { + pub fn new(name: S, locales: Vec, path_scheme: S) -> Self + where + S: ToString, + { + Self { + name: name.to_string(), + locales, + path_scheme: path_scheme.to_string(), + } + } +} + +impl RegistrySetup { + pub fn new( + name: &str, + file_sources: Vec, + locales: Vec, + ) -> Self { + Self { + name: name.to_string(), + file_sources, + locales, + } + } +} + +impl From for RegistrySetup { + fn from(s: fluent_testing::scenarios::structs::Scenario) -> Self { + Self { + name: s.name, + file_sources: s + .file_sources + .into_iter() + .map(|source| { + FileSource::new( + source.name, + source + .locales + .into_iter() + .map(|l| l.parse().unwrap()) + .collect(), + source.path_scheme, + ) + }) + .collect(), + locales: s + .locales + .into_iter() + .map(|loc| loc.parse().unwrap()) + .collect(), + } + } +} + +impl From<&fluent_testing::scenarios::structs::Scenario> for RegistrySetup { + fn from(s: &fluent_testing::scenarios::structs::Scenario) -> Self { + Self { + name: s.name.clone(), + file_sources: s + .file_sources + .iter() + .map(|source| { + FileSource::new( + source.name.clone(), + source.locales.iter().map(|l| l.parse().unwrap()).collect(), + source.path_scheme.clone(), + ) + }) + .collect(), + locales: s.locales.iter().map(|loc| loc.parse().unwrap()).collect(), + } + } +} + +#[derive(Default)] +struct InnerFileFetcher { + fs: MockFileSystem, +} + +#[derive(Clone)] +pub struct TestFileFetcher { + inner: Rc, +} + +impl TestFileFetcher { + pub fn new() -> Self { + Self { + inner: Rc::new(InnerFileFetcher::default()), + } + } + + pub fn get_test_file_source( + &self, + name: &str, + locales: Vec, + path: &str, + ) -> crate::source::FileSource { + crate::source::FileSource::new( + name.to_string(), + locales, + path.to_string(), + Default::default(), + self.clone(), + ) + } + + pub fn get_test_file_source_with_index( + &self, + name: &str, + locales: Vec, + path: &str, + index: Vec<&str>, + ) -> crate::source::FileSource { + crate::source::FileSource::new_with_index( + name.to_string(), + locales, + path.to_string(), + Default::default(), + self.clone(), + index.into_iter().map(|s| s.to_string()).collect(), + ) + } + + pub fn get_registry(&self, setup: S) -> L10nRegistry + where + S: Into, + { + self.get_registry_and_environment(setup).1 + } + + pub fn get_registry_and_environment( + &self, + setup: S, + ) -> ( + TestEnvironment, + L10nRegistry, + ) + where + S: Into, + { + let setup: RegistrySetup = setup.into(); + let provider = TestEnvironment::new(setup.locales); + + let reg = L10nRegistry::with_provider(provider.clone()); + let sources = setup + .file_sources + .into_iter() + .map(|source| { + let mut s = + self.get_test_file_source(&source.name, source.locales, &source.path_scheme); + s.set_reporter(provider.clone()); + s + }) + .collect(); + reg.register_sources(sources).unwrap(); + (provider, reg) + } + + pub fn get_registry_and_environment_with_adapter( + &self, + setup: S, + bundle_adapter: B, + ) -> (TestEnvironment, L10nRegistry) + where + S: Into, + B: BundleAdapter, + { + let setup: RegistrySetup = setup.into(); + let provider = TestEnvironment::new(setup.locales); + + let mut reg = L10nRegistry::with_provider(provider.clone()); + let sources = setup + .file_sources + .into_iter() + .map(|source| { + let mut s = + self.get_test_file_source(&source.name, source.locales, &source.path_scheme); + s.set_reporter(provider.clone()); + s + }) + .collect(); + reg.register_sources(sources).unwrap(); + reg.set_adapt_bundle(bundle_adapter) + .expect("Failed to set bundle adapter."); + (provider, reg) + } +} + +#[async_trait(?Send)] +impl FileFetcher for TestFileFetcher { + fn fetch_sync(&self, path: &str) -> std::io::Result { + self.inner.fs.get_test_file_sync(path) + } + + async fn fetch(&self, path: &str) -> std::io::Result { + self.inner.fs.get_test_file_async(path).await + } +} + +pub enum ErrorStrategy { + Panic, + Report, + Nothing, +} + +pub struct InnerTestEnvironment { + locales: Vec, + errors: Vec, + error_strategy: ErrorStrategy, +} + +#[derive(Clone)] +pub struct TestEnvironment { + inner: Rc>, +} + +impl TestEnvironment { + pub fn new(locales: Vec) -> Self { + Self { + inner: Rc::new(RefCell::new(InnerTestEnvironment { + locales, + errors: vec![], + error_strategy: ErrorStrategy::Report, + })), + } + } + + pub fn set_locales(&self, locales: Vec) { + self.inner.borrow_mut().locales = locales; + } + + pub fn errors(&self) -> Vec { + self.inner.borrow().errors.clone() + } + + pub fn clear_errors(&self) { + self.inner.borrow_mut().errors.clear() + } +} + +impl LocalesProvider for TestEnvironment { + type Iter = std::vec::IntoIter; + + fn locales(&self) -> Self::Iter { + self.inner.borrow().locales.clone().into_iter() + } +} + +impl ErrorReporter for TestEnvironment { + fn report_errors(&self, errors: Vec) { + match self.inner.borrow().error_strategy { + ErrorStrategy::Panic => { + panic!("Errors: {:#?}", errors); + } + ErrorStrategy::Report => { + println!("Errors: {:#?}", errors); + } + ErrorStrategy::Nothing => {} + } + self.inner.borrow_mut().errors.extend(errors); + } +} diff --git a/third_party/rust/l10nregistry/tests/localization.rs b/third_party/rust/l10nregistry/tests/localization.rs new file mode 100644 index 000000000000..8365ab3c6370 --- /dev/null +++ b/third_party/rust/l10nregistry/tests/localization.rs @@ -0,0 +1,183 @@ +use std::borrow::Cow; + +use fluent_fallback::{env::LocalesProvider, types::L10nKey, Localization}; +use l10nregistry::testing::{ + FileSource, MockBundleAdapter, RegistrySetup, TestEnvironment, TestFileFetcher, +}; +use serial_test::serial; +use unic_langid::{langid, LanguageIdentifier}; + +type L10nRegistry = l10nregistry::registry::L10nRegistry; + +static LOCALES: &[LanguageIdentifier] = &[langid!("pl"), langid!("en-US")]; +static mut FILE_FETCHER: Option = None; +static mut L10N_REGISTRY: Option = None; + +const FTL_RESOURCE: &str = "toolkit/updates/history.ftl"; +const L10N_ID_PL_EN: (&str, Option<&str>) = ("history-title", Some("Historia aktualizacji")); +const L10N_ID_MISSING: (&str, Option<&str>) = ("missing-id", None); +const L10N_ID_ONLY_EN: (&str, Option<&str>) = ( + "history-intro", + Some("The following updates have been installed"), +); + +fn get_file_fetcher() -> &'static TestFileFetcher { + let fetcher: &mut Option = unsafe { &mut FILE_FETCHER }; + + fetcher.get_or_insert_with(|| TestFileFetcher::new()) +} + +fn get_l10n_registry() -> &'static L10nRegistry { + let reg: &mut Option = unsafe { &mut L10N_REGISTRY }; + + reg.get_or_insert_with(|| { + let fetcher = get_file_fetcher(); + let setup = RegistrySetup::new( + "test", + vec![ + FileSource::new("toolkit", get_app_locales().to_vec(), "toolkit/{locale}/"), + FileSource::new("browser", get_app_locales().to_vec(), "browser/{locale}/"), + ], + get_app_locales().to_vec(), + ); + fetcher.get_registry_and_environment(setup).1 + }) +} + +fn get_app_locales() -> &'static [LanguageIdentifier] { + LOCALES +} + +struct LocalesService; + +impl LocalesProvider for LocalesService { + type Iter = std::vec::IntoIter; + + fn locales(&self) -> Self::Iter { + get_app_locales().to_vec().into_iter() + } +} + +fn sync_localization( + reg: &'static L10nRegistry, + res_ids: Vec, +) -> Localization { + Localization::with_env(res_ids, true, LocalesService, reg.clone()) +} + +fn async_localization( + reg: &'static L10nRegistry, + res_ids: Vec, +) -> Localization { + Localization::with_env(res_ids, false, LocalesService, reg.clone()) +} + +fn setup_sync_test() -> Localization { + sync_localization(get_l10n_registry(), vec![FTL_RESOURCE.into()]) +} + +fn setup_async_test() -> Localization { + async_localization(get_l10n_registry(), vec![FTL_RESOURCE.into()]) +} + +#[test] +#[serial] +fn localization_format_value_sync() { + let loc = setup_sync_test(); + let bundles = loc.bundles(); + let mut errors = vec![]; + + for query in &[L10N_ID_PL_EN, L10N_ID_MISSING, L10N_ID_ONLY_EN] { + let value = bundles.format_value_sync(query.0, None, &mut errors).unwrap(); + assert_eq!(value, query.1.map(|s| Cow::Borrowed(s))); + } + + assert_eq!(errors.len(), 4); +} + +#[test] +#[serial] +fn localization_format_values_sync() { + let loc = setup_sync_test(); + let bundles = loc.bundles(); + let mut errors = vec![]; + + let ids = &[L10N_ID_PL_EN, L10N_ID_MISSING, L10N_ID_ONLY_EN]; + let keys = ids + .iter() + .map(|query| L10nKey { + id: query.0.into(), + args: None, + }) + .collect::>(); + + let values = bundles.format_values_sync(&keys, &mut errors).unwrap(); + + assert_eq!(values.len(), ids.len()); + + for (value, query) in values.iter().zip(ids) { + if let Some(expected) = query.1 { + assert_eq!(*value, Some(Cow::Borrowed(expected))); + } + } + assert_eq!(errors.len(), 4); +} + +#[tokio::test] +#[serial] +async fn localization_format_value_async() { + let loc = setup_async_test(); + let bundles = loc.bundles(); + let mut errors = vec![]; + + for query in &[L10N_ID_PL_EN, L10N_ID_MISSING, L10N_ID_ONLY_EN] { + let value = bundles.format_value(query.0, None, &mut errors).await; + if let Some(expected) = query.1 { + assert_eq!(value, Some(Cow::Borrowed(expected))); + } + } +} + +#[tokio::test] +#[serial] +async fn localization_format_values_async() { + let loc = setup_async_test(); + let bundles = loc.bundles(); + let mut errors = vec![]; + + let ids = &[L10N_ID_PL_EN, L10N_ID_MISSING, L10N_ID_ONLY_EN]; + let keys = ids + .iter() + .map(|query| L10nKey { + id: query.0.into(), + args: None, + }) + .collect::>(); + + let values = bundles.format_values(&keys, &mut errors).await; + + assert_eq!(values.len(), ids.len()); + + for (value, query) in values.iter().zip(ids) { + if let Some(expected) = query.1 { + assert_eq!(*value, Some(Cow::Borrowed(expected))); + } + } +} + +#[tokio::test] +#[serial] +async fn localization_upgrade() { + let mut loc = setup_sync_test(); + let bundles = loc.bundles(); + let mut errors = vec![]; + let value = bundles + .format_value_sync(L10N_ID_PL_EN.0, None, &mut errors) + .unwrap(); + assert_eq!(value, L10N_ID_PL_EN.1.map(|s| Cow::Borrowed(s))); + + loc.set_async(); + let bundles = loc.bundles(); + let value = bundles.format_value(L10N_ID_PL_EN.0, None, &mut errors).await; + assert_eq!(value, L10N_ID_PL_EN.1.map(|s| Cow::Borrowed(s))); +} diff --git a/third_party/rust/l10nregistry/tests/registry.rs b/third_party/rust/l10nregistry/tests/registry.rs new file mode 100644 index 000000000000..a55ee1bd578a --- /dev/null +++ b/third_party/rust/l10nregistry/tests/registry.rs @@ -0,0 +1,199 @@ +use l10nregistry::testing::{FileSource, RegistrySetup, TestFileFetcher}; +use unic_langid::LanguageIdentifier; + +const FTL_RESOURCE_TOOLKIT: &str = "toolkit/global/textActions.ftl"; +const FTL_RESOURCE_BROWSER: &str = "branding/brand.ftl"; + +#[test] +fn test_generate_sources_for_file() { + let en_us: LanguageIdentifier = "en-US".parse().unwrap(); + let setup = RegistrySetup::new( + "test", + vec![ + FileSource::new("toolkit", vec![en_us.clone()], "toolkit/{locale}/"), + FileSource::new("browser", vec![en_us.clone()], "browser/{locale}/"), + ], + vec![en_us.clone()], + ); + let fetcher = TestFileFetcher::new(); + let (_, reg) = fetcher.get_registry_and_environment(setup); + + { + let lock = reg.lock(); + + let toolkit = lock.get_source("toolkit").unwrap(); + let browser = lock.get_source("browser").unwrap(); + + let mut i = lock.generate_sources_for_file(&en_us, FTL_RESOURCE_TOOLKIT); + + assert_eq!(i.next(), Some(toolkit)); + assert_eq!(i.next(), Some(browser)); + assert_eq!(i.next(), None); + + assert!(browser + .fetch_file_sync(&en_us, FTL_RESOURCE_TOOLKIT, false) + .is_none()); + + let mut i = lock.generate_sources_for_file(&en_us, FTL_RESOURCE_TOOLKIT); + assert_eq!(i.next(), Some(toolkit)); + assert_eq!(i.next(), None); + + assert!(toolkit + .fetch_file_sync(&en_us, FTL_RESOURCE_TOOLKIT, false) + .is_some()); + + let mut i = lock.generate_sources_for_file(&en_us, FTL_RESOURCE_TOOLKIT); + assert_eq!(i.next(), Some(toolkit)); + assert_eq!(i.next(), None); + } +} + +#[test] +fn test_generate_bundles_for_lang_sync() { + let en_us: LanguageIdentifier = "en-US".parse().unwrap(); + let setup = RegistrySetup::new( + "test", + vec![ + FileSource::new("toolkit", vec![en_us.clone()], "toolkit/{locale}/"), + FileSource::new("browser", vec![en_us.clone()], "browser/{locale}/"), + ], + vec![en_us.clone()], + ); + let fetcher = TestFileFetcher::new(); + let (_, reg) = fetcher.get_registry_and_environment(setup); + + let paths = vec![FTL_RESOURCE_TOOLKIT.into(), FTL_RESOURCE_BROWSER.into()]; + let mut i = reg.generate_bundles_for_lang_sync(en_us.clone(), paths); + + assert!(i.next().is_some()); + assert!(i.next().is_none()); +} + +#[test] +fn test_generate_bundles_sync() { + let en_us: LanguageIdentifier = "en-US".parse().unwrap(); + let setup = RegistrySetup::new( + "test", + vec![ + FileSource::new("toolkit", vec![en_us.clone()], "toolkit/{locale}/"), + FileSource::new("browser", vec![en_us.clone()], "browser/{locale}/"), + ], + vec![en_us.clone()], + ); + let fetcher = TestFileFetcher::new(); + let (_, reg) = fetcher.get_registry_and_environment(setup); + + let paths = vec![FTL_RESOURCE_TOOLKIT.into(), FTL_RESOURCE_BROWSER.into()]; + let lang_ids = vec![en_us]; + let mut i = reg.generate_bundles_sync(lang_ids.into_iter(), paths); + + assert!(i.next().is_some()); + assert!(i.next().is_none()); +} + +#[tokio::test] +async fn test_generate_bundles_for_lang() { + use futures::stream::StreamExt; + + let en_us: LanguageIdentifier = "en-US".parse().unwrap(); + let setup = RegistrySetup::new( + "test", + vec![ + FileSource::new("toolkit", vec![en_us.clone()], "toolkit/{locale}/"), + FileSource::new("browser", vec![en_us.clone()], "browser/{locale}/"), + ], + vec![en_us.clone()], + ); + let fetcher = TestFileFetcher::new(); + let (_, reg) = fetcher.get_registry_and_environment(setup); + + let paths = vec![FTL_RESOURCE_TOOLKIT.into(), FTL_RESOURCE_BROWSER.into()]; + let mut i = reg.generate_bundles_for_lang(en_us, paths); + + assert!(i.next().await.is_some()); + assert!(i.next().await.is_none()); +} + +#[tokio::test] +async fn test_generate_bundles() { + use futures::stream::StreamExt; + + let en_us: LanguageIdentifier = "en-US".parse().unwrap(); + let setup = RegistrySetup::new( + "test", + vec![ + FileSource::new("toolkit", vec![en_us.clone()], "toolkit/{locale}/"), + FileSource::new("browser", vec![en_us.clone()], "browser/{locale}/"), + ], + vec![en_us.clone()], + ); + let fetcher = TestFileFetcher::new(); + let (_, reg) = fetcher.get_registry_and_environment(setup); + + let paths = vec![FTL_RESOURCE_TOOLKIT.into(), FTL_RESOURCE_BROWSER.into()]; + let langs = vec![en_us]; + let mut i = reg.generate_bundles(langs.into_iter(), paths); + + assert!(i.next().await.is_some()); + assert!(i.next().await.is_none()); +} + +#[test] +fn test_manage_sources() { + let en_us: LanguageIdentifier = "en-US".parse().unwrap(); + let setup = RegistrySetup::new( + "test", + vec![ + FileSource::new("toolkit", vec![en_us.clone()], "toolkit/{locale}/"), + FileSource::new("browser", vec![en_us.clone()], "browser/{locale}/"), + ], + vec![en_us.clone()], + ); + let fetcher = TestFileFetcher::new(); + let (_, reg) = fetcher.get_registry_and_environment(setup); + + let lang_ids = vec![en_us]; + + let paths = vec![FTL_RESOURCE_TOOLKIT.into(), FTL_RESOURCE_BROWSER.into()]; + + let mut i = reg.generate_bundles_sync(lang_ids.clone().into_iter(), paths); + + assert!(i.next().is_some()); + assert!(i.next().is_none()); + + reg.clone() + .remove_sources(vec!["toolkit"]) + .expect("Failed to remove a source."); + + let paths = vec![FTL_RESOURCE_TOOLKIT.into(), FTL_RESOURCE_BROWSER.into()]; + let mut i = reg.generate_bundles_sync(lang_ids.clone().into_iter(), paths); + assert!(i.next().is_none()); + + let paths = vec![FTL_RESOURCE_BROWSER.into()]; + let mut i = reg.generate_bundles_sync(lang_ids.clone().into_iter(), paths); + assert!(i.next().is_some()); + assert!(i.next().is_none()); + + reg.register_sources(vec![fetcher.get_test_file_source( + "toolkit", + lang_ids.clone(), + "browser/{locale}/", + )]) + .expect("Failed to register a source."); + + let paths = vec![FTL_RESOURCE_TOOLKIT.into(), FTL_RESOURCE_BROWSER.into()]; + let mut i = reg.generate_bundles_sync(lang_ids.clone().into_iter(), paths); + assert!(i.next().is_none()); + + reg.update_sources(vec![fetcher.get_test_file_source( + "toolkit", + lang_ids.clone(), + "toolkit/{locale}/", + )]) + .expect("Failed to update a source."); + + let paths = vec![FTL_RESOURCE_TOOLKIT.into(), FTL_RESOURCE_BROWSER.into()]; + let mut i = reg.generate_bundles_sync(lang_ids.clone().into_iter(), paths); + assert!(i.next().is_some()); + assert!(i.next().is_none()); +} diff --git a/third_party/rust/l10nregistry/tests/scenarios.rs b/third_party/rust/l10nregistry/tests/scenarios.rs new file mode 100644 index 000000000000..f2a75b295c77 --- /dev/null +++ b/third_party/rust/l10nregistry/tests/scenarios.rs @@ -0,0 +1,55 @@ +use fluent_bundle::FluentArgs; +use fluent_fallback::Localization; +use fluent_testing::get_scenarios; +use l10nregistry::fluent::FluentBundle; +use l10nregistry::registry::BundleAdapter; +use l10nregistry::testing::{RegistrySetup, TestFileFetcher}; + +#[derive(Clone)] +struct ScenarioBundleAdapter {} + +impl BundleAdapter for ScenarioBundleAdapter { + fn adapt_bundle(&self, bundle: &mut FluentBundle) { + bundle.set_use_isolating(false); + bundle + .add_function("PLATFORM", |_positional, _named| "linux".into()) + .expect("Failed to add a function to the bundle."); + } +} + +#[test] +fn scenarios_test() { + let fetcher = TestFileFetcher::new(); + + let scenarios = get_scenarios(); + + let adapter = ScenarioBundleAdapter {}; + + for scenario in scenarios { + println!("scenario: {}", scenario.name); + let setup: RegistrySetup = (&scenario).into(); + let (env, reg) = fetcher.get_registry_and_environment_with_adapter(setup, adapter.clone()); + + let loc = Localization::with_env(scenario.res_ids.clone(), true, env.clone(), reg); + let bundles = loc.bundles(); + let mut errors = vec![]; + + for query in scenario.queries.iter() { + let args = query.input.args.as_ref().map(|args| { + let mut result = FluentArgs::new(); + for arg in args.as_slice() { + result.set(arg.id.clone(), arg.value.clone()); + } + result + }); + if let Some(output) = &query.output { + if let Some(value) = &output.value { + let v = bundles.format_value_sync(&query.input.id, args.as_ref(), &mut errors); + assert_eq!(v.unwrap().unwrap(), value.as_str()); + } + } + assert_eq!(errors, vec![]); + } + assert_eq!(env.errors(), vec![]); + } +} diff --git a/third_party/rust/l10nregistry/tests/source.rs b/third_party/rust/l10nregistry/tests/source.rs new file mode 100644 index 000000000000..7a3eb5ea3c77 --- /dev/null +++ b/third_party/rust/l10nregistry/tests/source.rs @@ -0,0 +1,141 @@ +use futures::future::join_all; +use l10nregistry::testing::TestFileFetcher; +use unic_langid::LanguageIdentifier; + +const FTL_RESOURCE_PRESENT: &str = "toolkit/global/textActions.ftl"; +const FTL_RESOURCE_MISSING: &str = "missing.ftl"; + +#[test] +fn test_fetch_sync() { + let fetcher = TestFileFetcher::new(); + let en_us: LanguageIdentifier = "en-US".parse().unwrap(); + + let fs1 = fetcher.get_test_file_source("toolkit", vec![en_us.clone()], "toolkit/{locale}/"); + + assert!(fs1 + .fetch_file_sync(&en_us, FTL_RESOURCE_PRESENT, false) + .is_some()); + assert!(fs1 + .fetch_file_sync(&en_us, FTL_RESOURCE_MISSING, false) + .is_none()); +} + +#[tokio::test] +async fn test_fetch_async() { + let fetcher = TestFileFetcher::new(); + let en_us: LanguageIdentifier = "en-US".parse().unwrap(); + + let fs1 = fetcher.get_test_file_source("toolkit", vec![en_us.clone()], "toolkit/{locale}/"); + + assert!(fs1.fetch_file(&en_us, FTL_RESOURCE_PRESENT).await.is_some()); + assert!(fs1.fetch_file(&en_us, FTL_RESOURCE_MISSING).await.is_none()); + assert!(fs1.fetch_file(&en_us, FTL_RESOURCE_PRESENT).await.is_some()); +} + +#[tokio::test] +async fn test_fetch_sync_2_async() { + let fetcher = TestFileFetcher::new(); + let en_us: LanguageIdentifier = "en-US".parse().unwrap(); + + let fs1 = fetcher.get_test_file_source("toolkit", vec![en_us.clone()], "toolkit/{locale}/"); + + assert!(fs1 + .fetch_file_sync(&en_us, FTL_RESOURCE_PRESENT, false) + .is_some()); + assert!(fs1.fetch_file(&en_us, FTL_RESOURCE_PRESENT).await.is_some()); + assert!(fs1 + .fetch_file_sync(&en_us, FTL_RESOURCE_PRESENT, false) + .is_some()); +} + +#[tokio::test] +async fn test_fetch_async_2_sync() { + let fetcher = TestFileFetcher::new(); + let en_us: LanguageIdentifier = "en-US".parse().unwrap(); + + let fs1 = fetcher.get_test_file_source("toolkit", vec![en_us.clone()], "toolkit/{locale}/"); + + assert!(fs1.fetch_file(&en_us, FTL_RESOURCE_PRESENT).await.is_some()); + assert!(fs1 + .fetch_file_sync(&en_us, FTL_RESOURCE_PRESENT, false) + .is_some()); +} + +#[test] +fn test_fetch_has_value_sync() { + let fetcher = TestFileFetcher::new(); + let en_us: LanguageIdentifier = "en-US".parse().unwrap(); + let path = FTL_RESOURCE_PRESENT; + let path_missing = FTL_RESOURCE_MISSING; + + let fs1 = fetcher.get_test_file_source("toolkit", vec![en_us.clone()], "toolkit/{locale}/"); + + assert_eq!(fs1.has_file(&en_us, path), None); + assert!(fs1.fetch_file_sync(&en_us, path, false).is_some()); + assert_eq!(fs1.has_file(&en_us, path), Some(true)); + + assert_eq!(fs1.has_file(&en_us, path_missing), None); + assert!(fs1.fetch_file_sync(&en_us, path_missing, false).is_none()); + assert_eq!(fs1.has_file(&en_us, path_missing), Some(false)); +} + +#[tokio::test] +async fn test_fetch_has_value_async() { + let fetcher = TestFileFetcher::new(); + let en_us: LanguageIdentifier = "en-US".parse().unwrap(); + let path = FTL_RESOURCE_PRESENT; + let path_missing = FTL_RESOURCE_MISSING; + + let fs1 = fetcher.get_test_file_source("toolkit", vec![en_us.clone()], "toolkit/{locale}/"); + + assert_eq!(fs1.has_file(&en_us, path), None); + assert!(fs1.fetch_file(&en_us, path).await.is_some()); + println!("Completed"); + assert_eq!(fs1.has_file(&en_us, path), Some(true)); + + assert_eq!(fs1.has_file(&en_us, path_missing), None); + assert!(fs1.fetch_file(&en_us, path_missing).await.is_none()); + assert_eq!(fs1.has_file(&en_us, path_missing), Some(false)); + assert!(fs1.fetch_file_sync(&en_us, path_missing, false).is_none()); +} + +#[tokio::test] +async fn test_fetch_async_consequitive() { + let fetcher = TestFileFetcher::new(); + let en_us: LanguageIdentifier = "en-US".parse().unwrap(); + + let fs1 = fetcher.get_test_file_source("toolkit", vec![en_us.clone()], "toolkit/{locale}/"); + + let results = join_all(vec![ + fs1.fetch_file(&en_us, FTL_RESOURCE_PRESENT), + fs1.fetch_file(&en_us, FTL_RESOURCE_PRESENT), + ]) + .await; + assert!(results[0].is_some()); + assert!(results[1].is_some()); + + assert!(fs1.fetch_file(&en_us, FTL_RESOURCE_PRESENT).await.is_some()); +} + +#[test] +fn test_indexed() { + let fetcher = TestFileFetcher::new(); + let en_us: LanguageIdentifier = "en-US".parse().unwrap(); + let path = FTL_RESOURCE_PRESENT; + let path_missing = FTL_RESOURCE_MISSING; + + let fs1 = fetcher.get_test_file_source_with_index( + "toolkit", + vec![en_us.clone()], + "toolkit/{locale}/", + vec!["toolkit/en-US/toolkit/global/textActions.ftl"], + ); + + assert_eq!(fs1.has_file(&en_us, path), Some(true)); + assert!(fs1.fetch_file_sync(&en_us, path, false).is_some()); + assert_eq!(fs1.has_file(&en_us, path), Some(true)); + + assert_eq!(fs1.has_file(&en_us, path_missing), Some(false)); + assert!(fs1.fetch_file_sync(&en_us, path_missing, false).is_none()); + assert_eq!(fs1.has_file(&en_us, path_missing), Some(false)); +} diff --git a/third_party/rust/ouroboros/.cargo-checksum.json b/third_party/rust/ouroboros/.cargo-checksum.json index b277f59fcd37..b8bc0b0005ca 100644 --- a/third_party/rust/ouroboros/.cargo-checksum.json +++ b/third_party/rust/ouroboros/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"c66eb0db7546bf21ba8363fd8e87cef56cb0452c57bc08a19278ca023ce4a3cb","src/lib.rs":"3d4ade09fc101227e57a2126d30748cc253baf8f69752b6a8173b0eb4da51cfc"},"package":"069fb33e127cabdc8ad6a287eed9719b85c612d36199777f6dc41ad91f7be41a"} \ No newline at end of file +{"files":{"Cargo.toml":"5c595572603e8e316678b3411c9a2b53db17754a0f082ee29c318e15e4ccec9f","src/lib.rs":"7ceedc701ace5cae672ee63068f87d229f801b24ed7126888f87568ca55e066f"},"package":"c8234affc3c31a8b744cc236fd3dc7443f57c6370cbb7d61f41f9c7dcc6c2530"} \ No newline at end of file diff --git a/third_party/rust/ouroboros/Cargo.toml b/third_party/rust/ouroboros/Cargo.toml index 036764728638..73048dbd3466 100644 --- a/third_party/rust/ouroboros/Cargo.toml +++ b/third_party/rust/ouroboros/Cargo.toml @@ -13,7 +13,7 @@ [package] edition = "2018" name = "ouroboros" -version = "0.8.0" +version = "0.9.2" authors = ["Joshua Maros "] description = "Easy, safe self-referential struct generation." documentation = "https://docs.rs/ouroboros" @@ -21,7 +21,7 @@ readme = "../README.md" license = "MIT OR Apache-2.0" repository = "https://github.com/joshua-maros/ouroboros" [dependencies.ouroboros_macro] -version = "0.8.0" +version = "0.9.2" [dependencies.stable_deref_trait] version = "1.2" diff --git a/third_party/rust/ouroboros/src/lib.rs b/third_party/rust/ouroboros/src/lib.rs index fb3031bce991..0ae80012a83b 100644 --- a/third_party/rust/ouroboros/src/lib.rs +++ b/third_party/rust/ouroboros/src/lib.rs @@ -8,7 +8,7 @@ /// This macro is used to turn a regular struct into a self-referencing one. An example: /// ```rust /// use ouroboros::self_referencing; -/// +/// /// #[self_referencing] /// struct MyStruct { /// int_data: Box, @@ -18,7 +18,7 @@ /// #[borrows(mut float_data)] /// float_reference: &'this mut f32, /// } -/// +/// /// fn main() { /// let mut my_value = MyStructBuilder { /// int_data: Box::new(42), @@ -26,7 +26,7 @@ /// int_reference_builder: |int_data: &i32| int_data, /// float_reference_builder: |float_data: &mut f32| float_data, /// }.build(); -/// +/// /// // Prints 42 /// println!("{:?}", my_value.borrow_int_data()); /// // Prints 3.14 @@ -35,7 +35,7 @@ /// my_value.with_mut(|fields| { /// **fields.float_reference = (**fields.int_reference as f32) * 2.0; /// }); -/// +/// /// // We can hold on to this reference... /// let int_ref = *my_value.borrow_int_reference(); /// println!("{:?}", *int_ref); @@ -50,7 +50,7 @@ /// - **immutably borrowed field**: a field which is immutably borrowed by at least one other field. /// - **mutably borrowed field**: a field which is mutably borrowed by exactly one other field. /// - **self-referencing field**: a field which borrows at least one other field. -/// - **head field**: a field which does not borrow any other fields, I.E. not self-referencing. +/// - **head field**: a field which does not borrow any other fields, I.E. not self-referencing. /// This does not include fields with empty borrows annotations (`#[borrows()]`.) /// - **tail field**: a field which is not borrowed by any other fields. /// @@ -63,7 +63,7 @@ /// the third needs to be borrowed mutably. You can also use `#[borrows()]` without any arguments to /// indicate a field that will eventually borrow from the struct, but does not borrow anything when /// first created. For example, you could use this on a field like `error: Option<&'this str>`. -/// +/// /// # You must comply with these limitations /// - Fields must be declared before the first time they are borrowed. /// - Normal borrowing rules apply, E.G. a field cannot be borrowed mutably twice. @@ -75,7 +75,55 @@ /// undefined at the location it was illegally used in. /// /// Violating them will result in an error message directly pointing out the violated rule. -/// +/// +/// # Async usage +/// All self-referencing structs can be initialized asynchronously by using either the +/// `MyStruct::new_async()` function or the `MyStructAsyncBuilder` builder. Due to limitations of +/// the rust compiler you closures must return a Future trait object wrapped in a `Pin>`. +/// +/// Here is the same example as above in its async version: +/// +/// ```ignore +/// use ouroboros::self_referencing; +/// +/// #[self_referencing] +/// struct MyStruct { +/// int_data: Box, +/// float_data: Box, +/// #[borrows(int_data)] +/// int_reference: &'this i32, +/// #[borrows(mut float_data)] +/// float_reference: &'this mut f32, +/// } +/// +/// #[tokio::main] +/// async fn main() { +/// let mut my_value = MyStructAsyncBuilder { +/// int_data: Box::new(42), +/// float_data: Box::new(3.14), +/// int_reference_builder: |int_data: &i32| Box::pin(async move { int_data }), +/// float_reference_builder: |float_data: &mut f32| Box::pin( async move { float_data }), +/// }.build().await; +/// +/// // Prints 42 +/// println!("{:?}", my_value.borrow_int_data()); +/// // Prints 3.14 +/// println!("{:?}", my_value.borrow_float_reference()); +/// // Sets the value of float_data to 84.0 +/// my_value.with_mut(|fields| { +/// **fields.float_reference = (**fields.int_reference as f32) * 2.0; +/// }); +/// +/// // We can hold on to this reference... +/// let int_ref = *my_value.borrow_int_reference(); +/// println!("{:?}", *int_ref); +/// // As long as the struct is still alive. +/// drop(my_value); +/// // This will cause an error! +/// // println!("{:?}", *int_ref); +/// } +/// ``` +/// /// # Flexibility of this crate /// The example above uses plain references as the self-referencing part of the struct, but you can /// use anything that is dependent on lifetimes of objects inside the struct. For example, you could @@ -130,7 +178,7 @@ /// ``` /// # Covariance /// Many types in Rust have a property called "covariance". In practical tearms, this means that a -/// covariant type like `Box<&'this i32>` can be used as a `Box<&'a i32>` as long as `'a` is +/// covariant type like `Box<&'this i32>` can be used as a `Box<&'a i32>` as long as `'a` is /// smaller than `'this`. Since the lifetime is smaller, it does not violate the lifetime specified /// by the original type. Contrast this to `Fn(&'this i32)`, which is not covariant. You cannot give /// this function a reference with a lifetime shorter than `'this` as the function needs something @@ -138,9 +186,9 @@ /// whether or not a type is covariant from inside the macro. As such, you may /// receive a compiler error letting you know that the macro is uncertain if a particular field /// uses a covariant type. Adding `#[covariant]` or `#[not_covariant]` will resolve this issue. -/// +/// /// These annotations control whether or not a `borrow_*` method is generated for that field. -/// +/// /// # Using `chain_hack` /// Unfortunately, as of September 2020, Rust has a /// [known limitation in its type checker](https://users.rust-lang.org/t/why-does-this-not-compile-box-t-target-t/49027/7?u=aaaaa) @@ -153,7 +201,7 @@ /// configurations may produce strange compiler errors. If you find such a configuration, please /// open an issue on the [Github repository](https://github.com/joshua-maros/ouroboros/issues). /// You can view a documented example of a struct which uses `chain_hack` [here](https://docs.rs/ouroboros_examples/latest/ouroboros_examples/struct.ChainHack.html). -/// +/// /// # What does the macro generate? /// The `#[self_referencing]` struct will replace your definition with an unsafe self-referencing /// struct with a safe public interface. Many functions will be generated depending on your original @@ -178,34 +226,48 @@ /// to the output. For **self-referencing fields**, you must provide a function or closure which creates /// the value based on the values it borrows. A field using the earlier example of /// `#[borrow(a, b, mut c)]` would require a function typed as -/// `FnOnce(a: &_, b: &_, c: &mut _) -> _`. Fields which have an empty borrows annotation -/// (`#[borrows()]`) should have their value directly passed in. A field using the earlier example +/// `FnOnce(a: &_, b: &_, c: &mut _) -> _`. Fields which have an empty borrows annotation +/// (`#[borrows()]`) should have their value directly passed in. A field using the earlier example /// of `Option<&'this str>` would require an input of `None`. Do not pass a function. Do not collect /// 200 dollars. +/// ### `MyStruct::new_async(fields...) -> MyStruct` +/// A basic async constructor. It works identically to the sync constructor differing only in the +/// type of closures it expects. Whenever a closure is required it is expected to return a Pinned +/// and Boxed Future that Outputs the same type as the synchronous version. /// ### `MyStructBuilder` /// This is the preferred way to create a new instance of your struct. It is similar to using the /// `MyStruct { a, b, c, d }` syntax instead of `MyStruct::new(a, b, c, d)`. It contains one field /// for every argument in the actual constructor. **Head fields** have the same name that you /// originally defined them with. **self-referencing fields** are suffixed with `_builder` since you need -/// to provide a function instead of a value. Fields with an empty borrows annotation are not +/// to provide a function instead of a value. Fields with an empty borrows annotation are not /// initialized using builders. Calling `.build()` on an instance of `MyStructBuilder` /// will convert it to an instance of `MyStruct`. +/// ### `MyStructAsyncBuilder` +/// This is the preferred way to asynchronously create a new instance of your struct. It works +/// identically to the synchronous builder differing only in the type of closures it expects. +/// Whenever a closure is required it is expected to return a Pinned and Boxed Future that Outputs +/// the same type as the synchronous version. /// ### `MyStruct::try_new(fields...) -> Result` /// Similar to the regular `new()` function, except the functions wich create values for all /// **self-referencing fields** can return `Result<>`s. If any of those are `Err`s, that error will be /// returned instead of an instance of `MyStruct`. The preferred way to use this function is through /// `MyStructTryBuilder` and its `try_build()` function. -/// ### `MyStruct::try_new_or_recover(fields...) -> Result` -/// Similar to the `try_new()` function, except that all the **head fields** are returned along side +/// ### `MyStruct::try_new_async(fields...) -> Result` +/// Similar to the regular `new_async()` function, except the functions wich create values for all +/// **self-referencing fields** can return `Result<>`s. If any of those are `Err`s, that error will be +/// returned instead of an instance of `MyStruct`. The preferred way to use this function is through +/// `MyStructAsyncTryBuilder` and its `try_build()` function. +/// ### `MyStruct::try_new_or_recover_async(fields...) -> Result` +/// Similar to the `try_new_async()` function, except that all the **head fields** are returned along side /// the original error in case of an error. The preferred way to use this function is through -/// `MyStructTryBuilder` and its `try_build_or_recover()` function. +/// `MyStructAsyncTryBuilder` and its `try_build_or_recover()` function. /// ### `MyStruct::with_FIELD(&self, user: FnOnce(field: &FieldType) -> R) -> R` -/// This function is generated for every **tail and immutably-borrowed field** in your struct. It +/// This function is generated for every **tail and immutably-borrowed field** in your struct. It /// allows safely accessing /// a reference to that value. The function generates the reference and passes it to `user`. You /// can do anything you want with the reference, it is constructed to not outlive the struct. /// ### `MyStruct::borrow_FIELD(&self) -> &FieldType` -/// This function is generated for every **tail and immutably-borrowed field** in your struct. It +/// This function is generated for every **tail and immutably-borrowed field** in your struct. It /// is equivalent to calling `my_struct.with_FIELD(|field| field)`. It is only generated for types /// which are known to be covariant, either through the macro being able to detect it or through the /// programmer adding the `#[covariant]` annotation to the field. @@ -230,6 +292,20 @@ pub mod macro_help { use stable_deref_trait::StableDeref; use std::ops::DerefMut; + pub struct CheckIfTypeIsStd(std::marker::PhantomData); + + macro_rules! std_type_check { + ($fn_name:ident $T:ident $check_for:ty) => { + impl<$T: ?Sized> CheckIfTypeIsStd<$check_for> { + pub fn $fn_name() { } + } + } + } + + std_type_check!(is_std_box_type T std::boxed::Box); + std_type_check!(is_std_arc_type T std::sync::Arc); + std_type_check!(is_std_rc_type T std::rc::Rc); + /// Converts a reference to an object implementing Deref to a static reference to the data it /// Derefs to. This is obviously unsafe because the compiler can no longer guarantee that the /// data outlives the reference. This function is templated to only work for containers that @@ -243,21 +319,25 @@ pub mod macro_help { /// /// The caller must ensure that the returned reference is not used after the originally passed /// reference would become invalid. - pub unsafe fn stable_deref_and_strip_lifetime<'a, T: StableDeref + 'static>( - data: &'a T, - ) -> &'static T::Target { + pub unsafe fn stable_deref_and_change_lifetime<'old, 'new: 'old, T: StableDeref + 'new>( + data: &'old T, + ) -> &'new T::Target { &*((&**data) as *const _) } - /// Like stable_deref_and_strip_lifetime, but for mutable references. + /// Like stable_deref_and_change_lifetime, but for mutable references. /// /// # Safety /// /// The caller must ensure that the returned reference is not used after the originally passed /// reference would become invalid. - pub unsafe fn stable_deref_and_strip_lifetime_mut<'a, T: StableDeref + DerefMut + 'static>( - data: &'a mut T, - ) -> &'static mut T::Target { + pub unsafe fn stable_deref_and_change_lifetime_mut< + 'old, + 'new: 'old, + T: StableDeref + DerefMut + 'new, + >( + data: &'old mut T, + ) -> &'new mut T::Target { &mut *((&mut **data) as *mut _) } } diff --git a/third_party/rust/ouroboros_macro/.cargo-checksum.json b/third_party/rust/ouroboros_macro/.cargo-checksum.json index 7dea200eb7a5..cc8148a0be4e 100644 --- a/third_party/rust/ouroboros_macro/.cargo-checksum.json +++ b/third_party/rust/ouroboros_macro/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"2c9faf7b268059bc04e1c709b4a6cc947baf1b10beaf62b48f7825e11682e371","src/lib.rs":"a63c40e91cb9379d2fbfd4f05bed931bc2c5a381aef24b1f7ffdabf6db367eec"},"package":"ad938cc920f299d6dce91e43d3ce316e785f4aa4bc4243555634dc2967098fc6"} \ No newline at end of file +{"files":{"Cargo.toml":"aa3b275d9c477318ca076a099bc1af242f70e00917dd9c10ee9a7465365d99de","src/lib.rs":"0e31da96a1e54b781a456e595d61887f7eb09c6920f06d769621bec747cbac40"},"package":"3633332cd8c0b6a865e2e0e705fad9cde25fe458cd0c693629b58a7b15e4d852"} \ No newline at end of file diff --git a/third_party/rust/ouroboros_macro/Cargo.toml b/third_party/rust/ouroboros_macro/Cargo.toml index 003d66d287a4..ad4f9e763ea4 100644 --- a/third_party/rust/ouroboros_macro/Cargo.toml +++ b/third_party/rust/ouroboros_macro/Cargo.toml @@ -13,7 +13,7 @@ [package] edition = "2018" name = "ouroboros_macro" -version = "0.8.0" +version = "0.9.2" authors = ["Joshua Maros "] description = "Proc macro for ouroboros crate." documentation = "https://docs.rs/ouroboros_macro" @@ -24,6 +24,7 @@ repository = "https://github.com/joshua-maros/ouroboros" proc-macro = true [dependencies.Inflector] version = "0.11" +default-features = false [dependencies.proc-macro-error] version = "1.0.4" diff --git a/third_party/rust/ouroboros_macro/src/lib.rs b/third_party/rust/ouroboros_macro/src/lib.rs index e87e6153ad72..ec4638e04ae5 100644 --- a/third_party/rust/ouroboros_macro/src/lib.rs +++ b/third_party/rust/ouroboros_macro/src/lib.rs @@ -5,8 +5,8 @@ use proc_macro2::{Group, Span, TokenTree}; use proc_macro_error::proc_macro_error; use quote::{format_ident, quote, ToTokens}; use syn::{ - Attribute, Error, Fields, GenericParam, Generics, Ident, ItemStruct, PathArguments, Type, - Visibility, + Attribute, Error, Fields, GenericArgument, GenericParam, Generics, Ident, ItemStruct, Lifetime, + PathArguments, Type, Visibility, WhereClause, }; #[derive(Clone, Copy, PartialEq)] @@ -61,7 +61,7 @@ impl StructFieldInfo { // ```rust // // Variable name taken from self.illegal_ref_name() // let test_illegal_static_reference = unsafe { - // ::ouroboros::macro_help::stable_deref_and_strip_lifetime( + // ::ouroboros::macro_help::stable_deref_and_change_lifetime( // &((*result.as_ptr()).field) // ) // }; @@ -71,7 +71,7 @@ impl StructFieldInfo { let ref_name = self.illegal_ref_name(); quote! { let #ref_name = unsafe { - ::ouroboros::macro_help::stable_deref_and_strip_lifetime( + ::ouroboros::macro_help::stable_deref_and_change_lifetime( &((*result.as_ptr()).#field_name) ) }; @@ -84,7 +84,7 @@ impl StructFieldInfo { let ref_name = self.illegal_ref_name(); quote! { let #ref_name = unsafe { - ::ouroboros::macro_help::stable_deref_and_strip_lifetime_mut( + ::ouroboros::macro_help::stable_deref_and_change_lifetime_mut( &mut ((*result.as_mut_ptr()).#field_name) ) }; @@ -107,6 +107,43 @@ impl StructFieldInfo { } } +const STD_CONTAINER_TYPES: &[&str] = &["Box", "Arc", "Rc"]; + +/// Returns Some((type_name, element_type)) if the provided type appears to be Box, Arc, or Rc from +/// the standard library. Returns None if not. +fn apparent_std_container_type(raw_type: &Type) -> Option<(&'static str, &Type)> { + let tpath = if let Type::Path(x) = raw_type { + x + } else { + return None; + }; + let segment = if let Some(segment) = tpath.path.segments.last() { + segment + } else { + return None; + }; + let args = if let PathArguments::AngleBracketed(args) = &segment.arguments { + args + } else { + return None; + }; + if args.args.len() != 1 { + return None; + } + let arg = args.args.first().unwrap(); + let eltype = if let GenericArgument::Type(x) = arg { + x + } else { + return None; + }; + for type_name in STD_CONTAINER_TYPES { + if segment.ident == type_name { + return Some((type_name, eltype)); + } + } + None +} + enum ArgType { /// Used when the initial value of a field can be passed directly into the constructor. Plain(TokenStream2), @@ -117,16 +154,8 @@ enum ArgType { fn deref_type(field_type: &Type, do_chain_hack: bool) -> Result { if do_chain_hack { - if let Type::Path(tpath) = field_type { - if let Some(segment) = tpath.path.segments.last() { - if segment.ident == "Box" || segment.ident == "Arc" || segment.ident == "Rc" { - if let PathArguments::AngleBracketed(args) = &segment.arguments { - if let Some(arg) = args.args.first() { - return Ok(quote! { #arg }); - } - } - } - } + if let Some((_std_type, eltype)) = apparent_std_container_type(field_type) { + return Ok(quote! { #eltype }); } Err(Error::new_spanned( &field_type, @@ -143,6 +172,7 @@ fn deref_type(field_type: &Type, do_chain_hack: bool) -> Result TokenStream2, do_chain_hack: bool, ) -> Result { @@ -150,7 +180,8 @@ fn make_constructor_arg_type_impl( if for_field.borrows.is_empty() { // Even if self_referencing is true, as long as borrows is empty, we don't need to use a // builder to construct it. - let field_type = replace_this_with_static(field_type.into_token_stream()); + let field_type = + replace_this_with_lifetime(field_type.into_token_stream(), fake_lifetime.clone()); Ok(ArgType::Plain(quote! { #field_type })) } else { let mut field_builder_params = Vec::new(); @@ -183,13 +214,23 @@ fn make_constructor_arg_type_impl( fn make_constructor_arg_type( for_field: &StructFieldInfo, other_fields: &[StructFieldInfo], + fake_lifetime: &Ident, do_chain_hack: bool, + make_async: bool, ) -> Result { let field_type = &for_field.typ; + let return_ty_constructor = || { + if make_async { + quote! { ::std::pin::Pin<::std::boxed::Box + 'this>> } + } else { + quote! { #field_type } + } + }; make_constructor_arg_type_impl( for_field, other_fields, - || quote! { #field_type }, + fake_lifetime, + return_ty_constructor, do_chain_hack, ) } @@ -198,13 +239,23 @@ fn make_constructor_arg_type( fn make_try_constructor_arg_type( for_field: &StructFieldInfo, other_fields: &[StructFieldInfo], + fake_lifetime: &Ident, do_chain_hack: bool, + make_async: bool, ) -> Result { let field_type = &for_field.typ; + let return_ty_constructor = || { + if make_async { + quote! { ::std::pin::Pin<::std::boxed::Box> + 'this>> } + } else { + quote! { ::core::result::Result<#field_type, Error_> } + } + }; make_constructor_arg_type_impl( for_field, other_fields, - || quote! { ::core::result::Result<#field_type, Error_> }, + fake_lifetime, + return_ty_constructor, do_chain_hack, ) } @@ -238,20 +289,20 @@ fn make_template_consumers(generics: &Generics) -> impl Iterator TokenStream2 { +fn replace_this_with_lifetime(input: TokenStream2, lifetime: Ident) -> TokenStream2 { input .into_iter() .map(|token| match &token { TokenTree::Ident(ident) => { if ident == "this" { - TokenTree::Ident(format_ident!("static")) + TokenTree::Ident(lifetime.clone()) } else { token } } TokenTree::Group(group) => TokenTree::Group(Group::new( group.delimiter(), - replace_this_with_static(group.stream()), + replace_this_with_lifetime(group.stream(), lifetime.clone()), )), _ => token, }) @@ -377,11 +428,8 @@ fn type_is_covariant(ty: &syn::Type, in_template: bool) -> bool { } let mut is_covariant = false; // If the type is Box, Arc, or Rc, we can assume it to be covariant. - let last = path.path.segments.last(); - if let Some(segment) = last { - if segment.ident == "Box" || segment.ident == "Arc" || segment.ident == "Rc" { - is_covariant = true; - } + if apparent_std_container_type(ty).is_some() { + is_covariant = true; } for segment in path.path.segments.iter() { let args = &segment.arguments; @@ -417,7 +465,7 @@ fn type_is_covariant(ty: &syn::Type, in_template: bool) -> bool { Reference(rf) => !in_template && type_is_covariant(&rf.elem, in_template), Slice(sl) => type_is_covariant(&sl.elem, in_template), // I don't think this is reachable but panic just in case. - TraitObject(..) => unimplemented!(), + TraitObject(..) => false, Tuple(tup) => { for ty in tup.elems.iter() { if !type_is_covariant(ty, in_template) { @@ -438,7 +486,7 @@ fn type_is_covariant(ty: &syn::Type, in_template: bool) -> bool { fn create_actual_struct( visibility: &Visibility, original_struct_def: &ItemStruct, -) -> Result<(TokenStream2, Vec), Error> { +) -> Result<(TokenStream2, Ident, Vec), Error> { let mut actual_struct_def = original_struct_def.clone(); actual_struct_def.vis = visibility.clone(); let mut field_info = Vec::new(); @@ -543,10 +591,19 @@ fn create_actual_struct( Fields::Unnamed(_fields) => unreachable!("Error handled earlier."), Fields::Unit => unreachable!("Error handled earlier."), } - // Finally, replace the fake 'this lifetime with 'static. - let actual_struct_def = replace_this_with_static(quote! { #actual_struct_def }); - Ok((actual_struct_def, field_info)) + let fake_lifetime = + if let Some(GenericParam::Lifetime(param)) = actual_struct_def.generics.params.first() { + param.lifetime.ident.clone() + } else { + format_ident!("static") + }; + + // Finally, replace the fake 'this lifetime with 'static. + let actual_struct_def = + replace_this_with_lifetime(quote! { #actual_struct_def }, fake_lifetime.clone()); + + Ok((actual_struct_def, fake_lifetime, field_info)) } // Takes the generics parameters from the original struct and turns them into arguments. @@ -572,12 +629,14 @@ fn create_builder_and_constructor( struct_visibility: &Visibility, struct_name: &Ident, builder_struct_name: &Ident, + fake_lifetime: &Ident, generic_params: &Generics, generic_args: &[TokenStream2], field_info: &[StructFieldInfo], do_chain_hack: bool, do_no_doc: bool, do_pub_extras: bool, + make_async: bool, ) -> Result<(TokenStream2, TokenStream2), Error> { let visibility = if do_pub_extras { struct_visibility.clone() @@ -627,7 +686,13 @@ fn create_builder_and_constructor( for field in field_info { let field_name = &field.name; - let arg_type = make_constructor_arg_type(&field, &field_info[..], do_chain_hack)?; + let arg_type = make_constructor_arg_type( + &field, + &field_info[..], + fake_lifetime, + do_chain_hack, + make_async, + )?; if let ArgType::Plain(plain_type) = arg_type { // No fancy builder function, we can just move the value directly into the struct. params.push(quote! { #field_name: #plain_type }); @@ -664,9 +729,13 @@ fn create_builder_and_constructor( } } doc_table += &format!(") -> {}: _` | \n", field_name.to_string()); - code.push(quote! { let #field_name = #builder_name (#(#builder_args),*); }); + if make_async { + code.push(quote! { let #field_name = #builder_name (#(#builder_args),*).await; }); + } else { + code.push(quote! { let #field_name = #builder_name (#(#builder_args),*); }); + } let generic_type_name = - format_ident!("{}Builder_", field_name.to_string().to_class_case()); + format_ident!("{}Builder_", to_class_case(field_name.to_string().as_str())); builder_struct_generic_producers.push(quote! { #generic_type_name: #bound_type }); builder_struct_generic_consumers.push(quote! { #generic_type_name }); @@ -674,7 +743,7 @@ fn create_builder_and_constructor( builder_struct_field_names.push(quote! { #builder_name }); } let field_type = &field.typ; - let field_type = replace_this_with_static(quote! { #field_type }); + let field_type = replace_this_with_lifetime(quote! { #field_type }, fake_lifetime.clone()); code.push(quote! { unsafe { ((&mut (*result.as_mut_ptr()).#field_name) as *mut #field_type).write(#field_name); }}); @@ -704,14 +773,37 @@ fn create_builder_and_constructor( quote! { #[doc(hidden)] } }; + let constructor_fn = if make_async { + quote! { async fn new_async } + } else { + quote! { fn new } + }; let constructor_def = quote! { #documentation - #visibility fn new(#(#params),*) -> Self { + #visibility #constructor_fn(#(#params),*) -> #struct_name <#(#generic_args),*> { #(#code)* unsafe { result.assume_init() } } }; let generic_where = &generic_params.where_clause; + let builder_fn = if make_async { + quote! { async fn build } + } else { + quote! { fn build } + }; + let builder_code = if make_async { + quote! { + #struct_name::new_async( + #(self.#builder_struct_field_names),* + ).await + } + } else { + quote! { + #struct_name::new( + #(self.#builder_struct_field_names),* + ) + } + }; let builder_def = quote! { #builder_documentation #visibility struct #builder_struct_name <#(#builder_struct_generic_producers),*> #generic_where { @@ -719,10 +811,8 @@ fn create_builder_and_constructor( } impl<#(#builder_struct_generic_producers),*> #builder_struct_name <#(#builder_struct_generic_consumers),*> #generic_where { #[doc=#build_fn_documentation] - #visibility fn build(self) -> #struct_name <#(#generic_args),*> { - #struct_name::new( - #(self.#builder_struct_field_names),* - ) + #visibility #builder_fn(self) -> #struct_name <#(#generic_args),*> { + #builder_code } } }; @@ -733,12 +823,14 @@ fn create_try_builder_and_constructor( struct_visibility: &Visibility, struct_name: &Ident, builder_struct_name: &Ident, + fake_lifetime: &Ident, generic_params: &Generics, generic_args: &[TokenStream2], field_info: &[StructFieldInfo], do_chain_hack: bool, do_no_doc: bool, do_pub_extras: bool, + make_async: bool, ) -> Result<(TokenStream2, TokenStream2), Error> { let visibility = if do_pub_extras { struct_visibility.clone() @@ -819,7 +911,13 @@ fn create_try_builder_and_constructor( for field in field_info { let field_name = &field.name; - let arg_type = make_try_constructor_arg_type(&field, &field_info[..], do_chain_hack)?; + let arg_type = make_try_constructor_arg_type( + &field, + &field_info[..], + fake_lifetime, + do_chain_hack, + make_async, + )?; if let ArgType::Plain(plain_type) = arg_type { // No fancy builder function, we can just move the value directly into the struct. params.push(quote! { #field_name: #plain_type }); @@ -862,15 +960,20 @@ fn create_try_builder_and_constructor( } } doc_table += &format!(") -> Result<{}: _, Error_>` | \n", field_name.to_string()); + let builder_value = if make_async { + quote! { #builder_name (#(#builder_args),*).await } + } else { + quote! { #builder_name (#(#builder_args),*) } + }; or_recover_code.push(quote! { - let #field_name = match #builder_name (#(#builder_args),*) { + let #field_name = match #builder_value { ::core::result::Result::Ok(value) => value, ::core::result::Result::Err(err) => return ::core::result::Result::Err((err, Heads { #(#head_recover_code),* })), }; }); let generic_type_name = - format_ident!("{}Builder_", field_name.to_string().to_class_case()); + format_ident!("{}Builder_", to_class_case(field_name.to_string().as_str())); builder_struct_generic_producers.push(quote! { #generic_type_name: #bound_type }); builder_struct_generic_consumers.push(quote! { #generic_type_name }); @@ -878,7 +981,7 @@ fn create_try_builder_and_constructor( builder_struct_field_names.push(quote! { #builder_name }); } let field_type = &field.typ; - let field_type = replace_this_with_static(quote! { #field_type }); + let field_type = replace_this_with_lifetime(quote! { #field_type }, fake_lifetime.clone()); let line = quote! { unsafe { ((&mut (*result.as_mut_ptr()).#field_name) as *mut #field_type).write(#field_name); }}; @@ -914,13 +1017,33 @@ fn create_try_builder_and_constructor( } else { quote! { #[doc(hidden)] } }; + let or_recover_ident = if make_async { + quote! { try_new_or_recover_async } + } else { + quote! { try_new_or_recover } + }; + let or_recover_constructor_fn = if make_async { + quote! { async fn #or_recover_ident } + } else { + quote! { fn #or_recover_ident } + }; + let constructor_fn = if make_async { + quote! { async fn try_new_async } + } else { + quote! { fn try_new } + }; + let constructor_code = if make_async { + quote! { #struct_name::#or_recover_ident(#(#builder_struct_field_names),*).await.map_err(|(error, _heads)| error) } + } else { + quote! { #struct_name::#or_recover_ident(#(#builder_struct_field_names),*).map_err(|(error, _heads)| error) } + }; let constructor_def = quote! { #documentation - #visibility fn try_new(#(#params),*) -> ::core::result::Result { - Self::try_new_or_recover(#(#builder_struct_field_names),*).map_err(|(error, _heads)| error) + #visibility #constructor_fn(#(#params),*) -> ::core::result::Result<#struct_name <#(#generic_args),*>, Error_> { + #constructor_code } #or_recover_documentation - #visibility fn try_new_or_recover(#(#params),*) -> ::core::result::Result)> { + #visibility #or_recover_constructor_fn(#(#params),*) -> ::core::result::Result<#struct_name <#(#generic_args),*>, (Error_, Heads<#(#generic_args),*>)> { #(#or_recover_code)* ::core::result::Result::Ok(unsafe { result.assume_init() }) } @@ -928,6 +1051,42 @@ fn create_try_builder_and_constructor( builder_struct_generic_producers.push(quote! { Error_ }); builder_struct_generic_consumers.push(quote! { Error_ }); let generic_where = &generic_params.where_clause; + let builder_fn = if make_async { + quote! { async fn try_build } + } else { + quote! { fn try_build } + }; + let or_recover_builder_fn = if make_async { + quote! { async fn try_build_or_recover } + } else { + quote! { fn try_build_or_recover } + }; + let builder_code = if make_async { + quote! { + #struct_name::try_new_async( + #(self.#builder_struct_field_names),* + ).await + } + } else { + quote! { + #struct_name::try_new( + #(self.#builder_struct_field_names),* + ) + } + }; + let or_recover_builder_code = if make_async { + quote! { + #struct_name::try_new_or_recover_async( + #(self.#builder_struct_field_names),* + ).await + } + } else { + quote! { + #struct_name::try_new_or_recover( + #(self.#builder_struct_field_names),* + ) + } + }; let builder_def = quote! { #builder_documentation #visibility struct #builder_struct_name <#(#builder_struct_generic_producers),*> #generic_where { @@ -935,16 +1094,12 @@ fn create_try_builder_and_constructor( } impl<#(#builder_struct_generic_producers),*> #builder_struct_name <#(#builder_struct_generic_consumers),*> #generic_where { #[doc=#build_fn_documentation] - #visibility fn try_build(self) -> ::core::result::Result<#struct_name <#(#generic_args),*>, Error_> { - #struct_name::try_new( - #(self.#builder_struct_field_names),* - ) + #visibility #builder_fn(self) -> ::core::result::Result<#struct_name <#(#generic_args),*>, Error_> { + #builder_code } #[doc=#build_or_recover_fn_documentation] - #visibility fn try_build_or_recover(self) -> ::core::result::Result<#struct_name <#(#generic_args),*>, (Error_, Heads<#(#generic_args),*>)> { - #struct_name::try_new_or_recover( - #(self.#builder_struct_field_names),* - ) + #visibility #or_recover_builder_fn(self) -> ::core::result::Result<#struct_name <#(#generic_args),*>, (Error_, Heads<#(#generic_args),*>)> { + #or_recover_builder_code } } }; @@ -1080,6 +1235,7 @@ fn make_with_functions( fn make_with_all_function( struct_visibility: &syn::Visibility, struct_name: &Ident, + fake_lifetime: &Ident, field_info: &[StructFieldInfo], generic_params: &Generics, generic_args: &[TokenStream2], @@ -1106,7 +1262,7 @@ fn make_with_all_function( mut_field_assignments.push(quote! { #field_name: &mut self.#field_name }); } else if field.field_type == FieldType::Borrowed { let ass = quote! { #field_name: unsafe { - ::ouroboros::macro_help::stable_deref_and_strip_lifetime( + ::ouroboros::macro_help::stable_deref_and_change_lifetime( &self.#field_name ) } }; @@ -1162,7 +1318,18 @@ fn make_with_all_function( ), struct_name.to_string() ); - let generic_where = &generic_params.where_clause; + let ltname = format!("'{}", fake_lifetime); + let lifetime = Lifetime::new(<name, Span::call_site()); + let generic_where = if let Some(clause) = &generic_params.where_clause { + let mut clause = clause.clone(); + let extra: WhereClause = syn::parse_quote! { where #lifetime: 'this }; + clause + .predicates + .push(extra.predicates.first().unwrap().clone()); + clause + } else { + syn::parse_quote! { where #lifetime: 'this } + }; let struct_defs = quote! { #[doc=#struct_documentation] #visibility struct BorrowedFields #new_generic_params #generic_where { #(#fields),* } @@ -1293,6 +1460,37 @@ fn make_into_heads( (heads_struct_def, into_heads_fn) } +fn make_type_asserts( + field_info: &[StructFieldInfo], + generic_params: &Generics, + _generic_args: &[TokenStream2], +) -> TokenStream2 { + let generic_where = &generic_params.where_clause; + let mut checks = Vec::new(); + for field in field_info { + let field_type = &field.typ; + if let Some((std_type, _eltype)) = apparent_std_container_type(field_type) { + let checker_name = match std_type { + "Box" => "is_std_box_type", + "Arc" => "is_std_arc_type", + "Rc" => "is_std_rc_type", + _ => unreachable!(), + }; + let checker_name = format_ident!("{}", checker_name); + let static_field_type = + replace_this_with_lifetime(quote! { #field_type }, format_ident!("static")); + checks.push(quote! { + ::ouroboros::macro_help::CheckIfTypeIsStd::<#static_field_type>::#checker_name(); + }); + } + } + quote! { + fn type_asserts #generic_params() #generic_where { + #(#checks)* + } + } +} + fn submodule_contents_visiblity(original_visibility: &Visibility) -> Visibility { match original_visibility { // inherited: allow parent of inner submodule to see @@ -1337,7 +1535,7 @@ fn self_referencing_impl( let visibility = &original_struct_def.vis; let submodule_contents_visiblity = submodule_contents_visiblity(visibility); - let (actual_struct_def, field_info) = + let (actual_struct_def, fake_lifetime, field_info) = create_actual_struct(&submodule_contents_visiblity, &original_struct_def)?; let generic_params = original_struct_def.generics.clone(); @@ -1348,30 +1546,63 @@ fn self_referencing_impl( &submodule_contents_visiblity, &struct_name, &builder_struct_name, + &fake_lifetime, &generic_params, &generic_args, &field_info[..], do_chain_hack, do_no_doc, do_pub_extras, + false, + )?; + let async_builder_struct_name = format_ident!("{}AsyncBuilder", struct_name); + let (async_builder_def, async_constructor_def) = create_builder_and_constructor( + &submodule_contents_visiblity, + &struct_name, + &async_builder_struct_name, + &fake_lifetime, + &generic_params, + &generic_args, + &field_info[..], + do_chain_hack, + do_no_doc, + do_pub_extras, + true, )?; let try_builder_struct_name = format_ident!("{}TryBuilder", struct_name); let (try_builder_def, try_constructor_def) = create_try_builder_and_constructor( &submodule_contents_visiblity, &struct_name, &try_builder_struct_name, + &fake_lifetime, &generic_params, &generic_args, &field_info[..], do_chain_hack, do_no_doc, do_pub_extras, + false, + )?; + let async_try_builder_struct_name = format_ident!("{}AsyncTryBuilder", struct_name); + let (async_try_builder_def, async_try_constructor_def) = create_try_builder_and_constructor( + &submodule_contents_visiblity, + &struct_name, + &async_try_builder_struct_name, + &fake_lifetime, + &generic_params, + &generic_args, + &field_info[..], + do_chain_hack, + do_no_doc, + do_pub_extras, + true, )?; let users = make_with_functions(&field_info[..], do_no_doc)?; let (with_all_struct_defs, with_all_fn_defs) = make_with_all_function( &submodule_contents_visiblity, struct_name, + &fake_lifetime, &field_info[..], &generic_params, &generic_args, @@ -1387,6 +1618,9 @@ fn self_referencing_impl( do_no_doc, do_pub_extras, ); + // These check that types like Box, Arc, and Rc refer to those types in the std lib and have not + // been overridden. + let type_asserts_def = make_type_asserts(&field_info[..], &generic_params, &generic_args); let extra_visibility = if do_pub_extras { visibility.clone() @@ -1401,20 +1635,27 @@ fn self_referencing_impl( use super::*; #actual_struct_def #builder_def + #async_builder_def #try_builder_def + #async_try_builder_def #with_all_struct_defs #heads_struct_def impl #generic_params #struct_name <#(#generic_args),*> #generic_where { #constructor_def + #async_constructor_def #try_constructor_def + #async_try_constructor_def #(#users)* #with_all_fn_defs #into_heads_fn } + #type_asserts_def } #visibility use #mod_name :: #struct_name; #extra_visibility use #mod_name :: #builder_struct_name; + #extra_visibility use #mod_name :: #async_builder_struct_name; #extra_visibility use #mod_name :: #try_builder_struct_name; + #extra_visibility use #mod_name :: #async_try_builder_struct_name; })) } @@ -1470,3 +1711,19 @@ pub fn self_referencing(attr: TokenStream, item: TokenStream) -> TokenStream { Err(err) => err.to_compile_error().into(), } } + +/// Functionality inspired by `Inflector`, reimplemented here to avoid the +/// `regex` dependency. +fn to_class_case(s: &str) -> String { + s.split('_') + .flat_map(|word| { + let mut chars = word.chars(); + let first = chars.next(); + // Unicode allows for a single character to become multiple characters when converting between cases. + first + .into_iter() + .flat_map(|c| c.to_uppercase()) + .chain(chars.flat_map(|c| c.to_lowercase())) + }) + .collect() +} diff --git a/third_party/rust/proc-macro-nested/.cargo-checksum.json b/third_party/rust/proc-macro-nested/.cargo-checksum.json new file mode 100644 index 000000000000..c71298236723 --- /dev/null +++ b/third_party/rust/proc-macro-nested/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"aa3269bb093b0d25256954f762d878102f57b461841a015d480d6fe656fc434b","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"d59d2723bbc1627b629429a9c779d5dcfde579c9723e6ded1052931c4e8efa0a","build.rs":"94935831bcf86515f37d66a033a7ea394112ccfe7e598cf3b406f0a0e09ad40c","src/lib.rs":"d7be075c69a4d35138d86a4510ec01e5dbf47df8b6b3a0c0d0478d142e248525"},"package":"bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086"} \ No newline at end of file diff --git a/third_party/rust/proc-macro-nested/Cargo.toml b/third_party/rust/proc-macro-nested/Cargo.toml new file mode 100644 index 000000000000..f7686893ef73 --- /dev/null +++ b/third_party/rust/proc-macro-nested/Cargo.toml @@ -0,0 +1,21 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies +# +# If you believe there's an error in this file please file an +# issue against the rust-lang/cargo repository. If you're +# editing this file be aware that the upstream Cargo.toml +# will likely look very different (and much more reasonable) + +[package] +name = "proc-macro-nested" +version = "0.1.7" +authors = ["David Tolnay "] +description = "Support for nested proc-macro-hack invocations" +license = "MIT OR Apache-2.0" +repository = "https://github.com/dtolnay/proc-macro-hack" +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/third_party/rust/proc-macro-nested/LICENSE-APACHE b/third_party/rust/proc-macro-nested/LICENSE-APACHE new file mode 100644 index 000000000000..16fe87b06e80 --- /dev/null +++ b/third_party/rust/proc-macro-nested/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/third_party/rust/proc-macro-nested/LICENSE-MIT b/third_party/rust/proc-macro-nested/LICENSE-MIT new file mode 100644 index 000000000000..f2675cdf1ea4 --- /dev/null +++ b/third_party/rust/proc-macro-nested/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2018 David Tolnay + +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/third_party/rust/proc-macro-nested/build.rs b/third_party/rust/proc-macro-nested/build.rs new file mode 100644 index 000000000000..01dbc0d58f3a --- /dev/null +++ b/third_party/rust/proc-macro-nested/build.rs @@ -0,0 +1,50 @@ +use std::env; +use std::fs; +use std::iter; +use std::path::{self, Path}; + +/* +#[doc(hidden)] +#[macro_export] +macro_rules! count { + () => { proc_macro_call_0!() }; + (!) => { proc_macro_call_1!() }; + (!!) => { proc_macro_call_2!() }; + ... +} +*/ + +fn main() { + // Tell Cargo not to rerun on src/lib.rs changes. + println!("cargo:rerun-if-changed=build.rs"); + + let mut content = String::new(); + content += "#[doc(hidden)]\n"; + content += "#[macro_export]\n"; + content += "macro_rules! count {\n"; + for i in 0..=64 { + let bangs = iter::repeat("!").take(i).collect::(); + content += &format!(" ({}) => {{ proc_macro_call_{}!() }};\n", bangs, i); + } + content += " ($(!)+) => {\n"; + content += " compile_error! {\n"; + content += " \"this macro does not support >64 nested macro invocations\"\n"; + content += " }\n"; + content += " };\n"; + content += "}\n"; + + let content = content.as_bytes(); + let out_dir = env::var("OUT_DIR").unwrap(); + let ref dest_path = Path::new(&out_dir).join("count.rs"); + + // Avoid bumping filetime if content is up to date. Possibly related to + // https://github.com/dtolnay/proc-macro-hack/issues/56 ...? + if fs::read(dest_path) + .map(|existing| existing != content) + .unwrap_or(true) + { + fs::write(dest_path, content).unwrap(); + } + + println!("cargo:rustc-env=PATH_SEPARATOR={}", path::MAIN_SEPARATOR); +} diff --git a/third_party/rust/proc-macro-nested/src/lib.rs b/third_party/rust/proc-macro-nested/src/lib.rs new file mode 100644 index 000000000000..0a8a6750f166 --- /dev/null +++ b/third_party/rust/proc-macro-nested/src/lib.rs @@ -0,0 +1,69 @@ +//! Support for nested invocations of proc-macro-hack expression macros. +//! +//! By default, macros defined through proc-macro-hack do not support nested +//! invocations, i.e. the code emitted by a proc-macro-hack macro invocation +//! cannot contain recursive calls to the same proc-macro-hack macro nor calls +//! to any other proc-macro-hack macros. +//! +//! This crate provides opt-in support for such nested invocations. +//! +//! To make a macro callable recursively, add a dependency on this crate from +//! your declaration crate and update the `#[proc_macro_hack]` re-export as +//! follows. +//! +//! ``` +//! // Before +//! # const IGNORE: &str = stringify! { +//! #[proc_macro_hack] +//! pub use demo_hack_impl::add_one; +//! # }; +//! ``` +//! +//! ``` +//! // After +//! # const IGNORE: &str = stringify! { +//! #[proc_macro_hack(support_nested)] +//! pub use demo_hack_impl::add_one; +//! # }; +//! ``` +//! +//! No change is required within your definition crate, only to the re-export in +//! the declaration crate. +//! +//! # Limitations +//! +//! - Nested invocations are preprocessed by a TT-muncher, so the caller's crate +//! will be required to contain `#![recursion_limit = "..."]` if there are +//! lengthy macro invocations. +//! +//! - Only up to 64 nested invocations are supported. + +#![no_std] + +include!(concat!(env!("OUT_DIR"), env!("PATH_SEPARATOR"), "count.rs")); + +#[doc(hidden)] +#[macro_export] +macro_rules! dispatch { + (() $($bang:tt)*) => { + $crate::count!($($bang)*) + }; + ((($($first:tt)*) $($rest:tt)*) $($bang:tt)*) => { + $crate::dispatch!(($($first)* $($rest)*) $($bang)*) + }; + (([$($first:tt)*] $($rest:tt)*) $($bang:tt)*) => { + $crate::dispatch!(($($first)* $($rest)*) $($bang)*) + }; + (({$($first:tt)*} $($rest:tt)*) $($bang:tt)*) => { + $crate::dispatch!(($($first)* $($rest)*) $($bang)*) + }; + ((! $($rest:tt)*) $($bang:tt)*) => { + $crate::dispatch!(($($rest)*) $($bang)* !) + }; + ((!= $($rest:tt)*) $($bang:tt)*) => { + $crate::dispatch!(($($rest)*) $($bang)* !) + }; + (($first:tt $($rest:tt)*) $($bang:tt)*) => { + $crate::dispatch!(($($rest)*) $($bang)*) + }; +} diff --git a/third_party/rust/replace_with/.cargo-checksum.json b/third_party/rust/replace_with/.cargo-checksum.json new file mode 100644 index 000000000000..d06f44d08581 --- /dev/null +++ b/third_party/rust/replace_with/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"c84c56d27d6113b6b1c081aa2377801ed9dc223e63b32de8bfda3d645d601282","LICENSE-APACHE.txt":"8173d5c29b4f956d532781d2b86e4e30f83e6b7878dce18c919451d6ba707c90","LICENSE-MIT.txt":"23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3","README.md":"2b55e25c35977c9d196ca42883df76ad5c721ea3fa6ef45b9548cc0499d1392d","azure-pipelines.yml":"3c0c697b1584f8933af67fa08478b07e9ce1fb2a4f35ed2058fbbea4bfc45354","src/lib.rs":"476cf27a89c488e68de4813dfbe08a6a5ec765802ab911ca406d1e9a67592b66"},"package":"e3a8614ee435691de62bcffcf4a66d91b3594bf1428a5722e79103249a095690"} \ No newline at end of file diff --git a/third_party/rust/replace_with/Cargo.toml b/third_party/rust/replace_with/Cargo.toml new file mode 100644 index 000000000000..2fbd957b37fc --- /dev/null +++ b/third_party/rust/replace_with/Cargo.toml @@ -0,0 +1,38 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies +# +# If you believe there's an error in this file please file an +# issue against the rust-lang/cargo repository. If you're +# editing this file be aware that the upstream Cargo.toml +# will likely look very different (and much more reasonable) + +[package] +edition = "2018" +name = "replace_with" +version = "0.1.7" +authors = ["Alec Mocatta "] +description = "Temporarily take ownership of a value at a mutable location, and replace it with a new value based on the old one.\n" +homepage = "https://github.com/alecmocatta/replace_with" +documentation = "https://docs.rs/replace_with" +readme = "README.md" +keywords = ["mutability"] +categories = ["rust-patterns"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/alecmocatta/replace_with" + +[features] +default = ["std"] +nightly = [] +panic_abort = [] +std = [] +[badges.azure-devops] +build = "11" +pipeline = "tests" +project = "alecmocatta/replace_with" + +[badges.maintenance] +status = "actively-developed" diff --git a/third_party/rust/replace_with/LICENSE-APACHE.txt b/third_party/rust/replace_with/LICENSE-APACHE.txt new file mode 100644 index 000000000000..11069edd7901 --- /dev/null +++ b/third_party/rust/replace_with/LICENSE-APACHE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/third_party/rust/replace_with/LICENSE-MIT.txt b/third_party/rust/replace_with/LICENSE-MIT.txt new file mode 100644 index 000000000000..31aa79387f27 --- /dev/null +++ b/third_party/rust/replace_with/LICENSE-MIT.txt @@ -0,0 +1,23 @@ +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/third_party/rust/replace_with/README.md b/third_party/rust/replace_with/README.md new file mode 100644 index 000000000000..a04c2d98ebe3 --- /dev/null +++ b/third_party/rust/replace_with/README.md @@ -0,0 +1,117 @@ +# replace_with + +[![Crates.io](https://img.shields.io/crates/v/replace_with.svg?maxAge=86400)](https://crates.io/crates/replace_with) +[![MIT / Apache 2.0 licensed](https://img.shields.io/crates/l/replace_with.svg?maxAge=2592000)](#License) +[![Build Status](https://dev.azure.com/alecmocatta/replace_with/_apis/build/status/tests?branchName=master)](https://dev.azure.com/alecmocatta/replace_with/_build?definitionId=11) + +[📖 Docs](https://docs.rs/replace_with) | [💬 Chat](https://constellation.zulipchat.com/#narrow/stream/213236-subprojects) + +Temporarily take ownership of a value at a mutable location, and replace it with a new value based on the old one. + +This crate provides the function [`replace_with()`](https://docs.rs/replace_with/0.1/replace_with/fn.replace_with.html), which is like [`std::mem::replace()`](https://doc.rust-lang.org/std/mem/fn.replace.html) except it allows the replacement value to be mapped from the original value. + +See [RFC 1736](https://github.com/rust-lang/rfcs/pull/1736) for a lot of discussion as to its merits. It was never merged, and the desired ability to temporarily move out of `&mut T` doesn't exist yet, so this crate is my interim solution. + +It's very akin to [`take_mut`](https://github.com/Sgeo/take_mut), though uses `Drop` instead of [`std::panic::catch_unwind()`](https://doc.rust-lang.org/std/panic/fn.catch_unwind.html) to react to unwinding, which avoids the optimisation barrier of calling the `extern "C" __rust_maybe_catch_panic()`. As such it's up to ∞x faster. The API also attempts to make slightly more explicit the behavior on panic – [`replace_with()`](https://docs.rs/replace_with/0.1/replace_with/fn.replace_with.html) accepts two closures such that aborting in the "standard case" where the mapping closure (`FnOnce(T) -> T`) panics (as [`take_mut::take()`](https://docs.rs/take_mut/0.2.2/take_mut/fn.take.html) does) is avoided. If the second closure (`FnOnce() -> T`) panics, however, then it does indeed abort. The "abort on first panic" behaviour is available with [`replace_with_or_abort()`](https://docs.rs/replace_with/0.1/replace_with/fn.replace_with_or_abort.html). + +## Example + +Consider this motivating example: + +```rust +enum States { + A(String), + B(String), +} + +impl States { + fn poll(&mut self) { + // error[E0507]: cannot move out of borrowed content + *self = match *self { + // ^^^^^ cannot move out of borrowed content + States::A(a) => States::B(a), + States::B(a) => States::A(a), + }; + } +} +``` + +Depending on context this can be quite tricky to work around. With this crate, however: + +```rust +enum States { + A(String), + B(String), +} + +impl States { + fn poll(&mut self) { + replace_with_or_abort(self, |self_| match self_ { + States::A(a) => States::B(a), + States::B(a) => States::A(a), + }); + } +} +``` + +Huzzah! + +## `no_std` + +To use `replace_with` with `no_std` you have to disable the `std` feature, which is active by default, by specifying your dependency to it like this: + +```toml +# Cargo.toml + +[dependencies.replace_with] +version = ... +default-features = false +features = [] +... +``` + +The [`replace_with()`](https://docs.rs/replace_with/0.1/replace_with/fn.replace_with.html) & [`replace_with_or_default()`](https://docs.rs/replace_with/0.1/replace_with/fn.replace_with_or_default.html) functions are available on stable Rust both, with and without `std`. + +The [`replace_with_or_abort()`](https://docs.rs/replace_with/0.1/replace_with/fn.replace_with_or_abort.html) function however by default makes use of [`std::process::abort()`](https://doc.rust-lang.org/std/process/fn.abort.html) which is not available with `no_std`. + +As such `replace_with` will by default call [`core::intrinsics::abort()`](https://doc.rust-lang.org/core/intrinsics/fn.abort.html) instead, which in turn requires nightly Rust. + +Not everything is lost for stable `no_std` though, `replace_with` has one more trick up its sleeve: + +### panic = "abort" + +If you define [`panic = abort`](https://doc.rust-lang.org/edition-guide/rust-2018/error-handling-and-panics/aborting-on-panic.html) in the `[profile]` section of your crate's `Cargo.toml` … + +```toml +# Cargo.toml + +[profile.debug] +panic = "abort" + +[profile.release] +panic = "abort" +``` + +… and add the `"panic_abort"` feature to `replace_with` in the `dependencies` section of your crate's `Cargo.toml` … + +```toml +# Cargo.toml + +[dependencies.replace_with] +features = ["panic_abort"] +... +``` + +… the `"panic_abort"` feature enables the [`replace_with_or_abort_unchecked()`](https://docs.rs/replace_with/0.1/replace_with/fn.replace_with_or_abort_unchecked.html) function becomes on stable Rust as an `unsafe` function, a simple wrapper around `ptr::write(dest, f(ptr::read(dest)));`. + +**Word of caution:** It is crucial to only ever use this function having defined `panic = "abort"`, or else bad things may happen. It's *up to you* to uphold this invariant! + +## License +Licensed under either of + + * Apache License, Version 2.0, ([LICENSE-APACHE.txt](LICENSE-APACHE.txt) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT.txt](LICENSE-MIT.txt) or http://opensource.org/licenses/MIT) + +at your option. + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/third_party/rust/replace_with/azure-pipelines.yml b/third_party/rust/replace_with/azure-pipelines.yml new file mode 100644 index 000000000000..133937a0d3a0 --- /dev/null +++ b/third_party/rust/replace_with/azure-pipelines.yml @@ -0,0 +1,32 @@ +trigger: ["master"] +pr: ["master"] + +resources: + repositories: + - repository: templates + type: github + name: alecmocatta/azure-pipeline-templates + endpoint: alecmocatta + +jobs: +- template: rust.yml@templates + parameters: + endpoint: alecmocatta + default: + rust_toolchain: 1.21.0 stable beta nightly + rust_lint_toolchain: nightly-2020-07-12 + rust_flags: '' + rust_features: ';default;all' + rust_target_check: '' + rust_target_build: '' + rust_target_run: '' + matrix: + windows: + imageName: 'windows-latest' + rust_target_run: 'x86_64-pc-windows-msvc' + mac: + imageName: 'macos-latest' + rust_target_run: 'x86_64-apple-darwin' + linux: + imageName: 'ubuntu-latest' + rust_target_run: 'x86_64-unknown-linux-gnu' diff --git a/third_party/rust/replace_with/src/lib.rs b/third_party/rust/replace_with/src/lib.rs new file mode 100755 index 000000000000..05ab0fbf04b8 --- /dev/null +++ b/third_party/rust/replace_with/src/lib.rs @@ -0,0 +1,596 @@ +//! Temporarily take ownership of a value at a mutable location, and replace it with a new value +//! based on the old one. +//! +//!

+//! 📦  Crates.io  │  📑  GitHub  │  💬  Chat +//!

+//! +//! This crate provides the function [`replace_with()`], which is like [`std::mem::replace()`] +//! except it allows the replacement value to be mapped from the original value. +//! +//! See [RFC 1736](https://github.com/rust-lang/rfcs/pull/1736) for a lot of discussion as to its +//! merits. It was never merged, and the desired ability to temporarily move out of `&mut T` doesn't +//! exist yet, so this crate is my interim solution. +//! +//! It's very akin to [`take_mut`](https://github.com/Sgeo/take_mut), though uses `Drop` instead of +//! [`std::panic::catch_unwind()`] to react to unwinding, which avoids the optimisation barrier of +//! calling the `extern "C" __rust_maybe_catch_panic()`. As such it's up to ∞x faster. The API also +//! attempts to make slightly more explicit the behavior on panic – [`replace_with()`] accepts two +//! closures such that aborting in the "standard case" where the mapping closure (`FnOnce(T) -> T`) +//! panics (as [`take_mut::take()`](https://docs.rs/take_mut/0.2.2/take_mut/fn.take.html) does) is +//! avoided. If the second closure (`FnOnce() -> T`) panics, however, then it does indeed abort. +//! The "abort on first panic" behaviour is available with [`replace_with_or_abort()`]. +//! +//! # Example +//! +//! Consider this motivating example: +//! +//! ```compile_fail +//! # use replace_with::*; +//! enum States { +//! A(String), +//! B(String), +//! } +//! +//! impl States { +//! fn poll(&mut self) { +//! // error[E0507]: cannot move out of borrowed content +//! *self = match *self { +//! // ^^^^^ cannot move out of borrowed content +//! States::A(a) => States::B(a), +//! States::B(a) => States::A(a), +//! }; +//! } +//! } +//! ``` +//! +//! Depending on context this can be quite tricky to work around. With this crate, however: +//! +//! ``` +//! # use replace_with::*; +//! enum States { +//! A(String), +//! B(String), +//! } +//! +//! # #[cfg(any(feature = "std", feature = "nightly"))] +//! impl States { +//! fn poll(&mut self) { +//! replace_with_or_abort(self, |self_| match self_ { +//! States::A(a) => States::B(a), +//! States::B(a) => States::A(a), +//! }); +//! } +//! } +//! ``` +//! +//! Huzzah! + +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr( + all(not(feature = "std"), feature = "nightly"), + feature(core_intrinsics) +)] +#![doc(html_root_url = "https://docs.rs/replace_with/0.1.7")] +#![warn( + missing_copy_implementations, + missing_debug_implementations, + missing_docs, + trivial_casts, + trivial_numeric_casts, + unused_import_braces, + unused_qualifications, + unused_results, + // clippy::pedantic +)] +// #![allow(clippy::inline_always)] + +#[cfg(not(feature = "std"))] +use core as std; + +use std::{mem, ptr}; + +struct OnDrop(mem::ManuallyDrop); +impl Drop for OnDrop { + #[inline(always)] + fn drop(&mut self) { + (unsafe { ptr::read(&*self.0) })(); + } +} + +#[doc(hidden)] +#[inline(always)] +pub fn on_unwind T, T, P: FnOnce()>(f: F, p: P) -> T { + let x = OnDrop(mem::ManuallyDrop::new(p)); + let t = f(); + let mut x = mem::ManuallyDrop::new(x); + unsafe { mem::ManuallyDrop::drop(&mut x.0) }; + t +} + +#[doc(hidden)] +#[inline(always)] +pub fn on_return_or_unwind T, T, P: FnOnce()>(f: F, p: P) -> T { + let _x = OnDrop(mem::ManuallyDrop::new(p)); + f() +} + +/// Temporarily takes ownership of a value at a mutable location, and replace it with a new value +/// based on the old one. +/// +/// We move out of the reference temporarily, to apply a closure `f`, returning a new value, which +/// is then placed at the original value's location. +/// +/// # An important note +/// +/// On panic (or to be more precise, unwinding) of the closure `f`, `default` will be called to +/// provide a replacement value. `default` should not panic – doing so will constitute a double +/// panic and will most likely abort the process. +/// +/// # Example +/// +/// ``` +/// # use replace_with::*; +/// enum States { +/// A(String), +/// B(String), +/// } +/// +/// impl States { +/// fn poll(&mut self) { +/// replace_with( +/// self, +/// || States::A(String::new()), +/// |self_| match self_ { +/// States::A(a) => States::B(a), +/// States::B(a) => States::A(a), +/// }, +/// ); +/// } +/// } +/// ``` +#[inline] +pub fn replace_with T, F: FnOnce(T) -> T>(dest: &mut T, default: D, f: F) { + unsafe { + let old = ptr::read(dest); + let new = on_unwind(move || f(old), || ptr::write(dest, default())); + ptr::write(dest, new); + } +} + +/// Temporarily takes ownership of a value at a mutable location, and replace it with a new value +/// based on the old one. Replaces with [`Default::default()`] on panic. +/// +/// We move out of the reference temporarily, to apply a closure `f`, returning a new value, which +/// is then placed at the original value's location. +/// +/// # An important note +/// +/// On panic (or to be more precise, unwinding) of the closure `f`, `T::default()` will be called to +/// provide a replacement value. `T::default()` should not panic – doing so will constitute a double +/// panic and will most likely abort the process. +/// +/// Equivalent to `replace_with(dest, T::default, f)`. +/// +/// Differs from `*dest = mem::replace(dest, Default::default())` in that `Default::default()` will +/// only be called on panic. +/// +/// # Example +/// +/// ``` +/// # use replace_with::*; +/// enum States { +/// A(String), +/// B(String), +/// } +/// +/// impl Default for States { +/// fn default() -> Self { +/// States::A(String::new()) +/// } +/// } +/// +/// impl States { +/// fn poll(&mut self) { +/// replace_with_or_default(self, |self_| match self_ { +/// States::A(a) => States::B(a), +/// States::B(a) => States::A(a), +/// }); +/// } +/// } +/// ``` +#[inline] +pub fn replace_with_or_default T>(dest: &mut T, f: F) { + replace_with(dest, T::default, f); +} + +/// Temporarily takes ownership of a value at a mutable location, and replace it with a new value +/// based on the old one. Aborts on panic. +/// +/// We move out of the reference temporarily, to apply a closure `f`, returning a new value, which +/// is then placed at the original value's location. +/// +/// # An important note +/// +/// On panic (or to be more precise, unwinding) of the closure `f`, the process will **abort** to +/// avoid returning control while `dest` is in a potentially invalid state. +/// +/// If this behaviour is undesirable, use [`replace_with`] or [`replace_with_or_default`]. +/// +/// Equivalent to `replace_with(dest, || process::abort(), f)`. +/// +/// # Example +/// +/// ``` +/// # use replace_with::*; +/// enum States { +/// A(String), +/// B(String), +/// } +/// +/// # #[cfg(any(feature = "std", feature = "nightly"))] +/// impl States { +/// fn poll(&mut self) { +/// replace_with_or_abort(self, |self_| match self_ { +/// States::A(a) => States::B(a), +/// States::B(a) => States::A(a), +/// }); +/// } +/// } +/// ``` +#[inline] +#[cfg(feature = "std")] +pub fn replace_with_or_abort T>(dest: &mut T, f: F) { + replace_with(dest, || std::process::abort(), f); +} + +#[inline] +#[cfg(all(not(feature = "std"), feature = "nightly"))] +pub fn replace_with_or_abort T>(dest: &mut T, f: F) { + replace_with(dest, || unsafe { std::intrinsics::abort() }, f); +} + +/// Temporarily takes ownership of a value at a mutable location, and replace it with a new value +/// based on the old one. Aborts on panic. +/// +/// We move out of the reference temporarily, to apply a closure `f`, returning a new value, which +/// is then placed at the original value's location. +/// +/// # An important note +/// +/// On panic (or to be more precise, unwinding) of the closure `f`, the process will **abort** to +/// avoid returning control while `dest` is in a potentially invalid state. +/// +/// Unlike for `replace_with_or_abort()` users of `replace_with_or_abort_unchecked()` are expected +/// to have `features = ["panic_abort", …]` defined in `Cargo.toml` +/// and `panic = "abort"` defined in their profile for it to behave semantically correct: +/// +/// ```toml +/// # Cargo.toml +/// +/// [profile.debug] +/// panic = "abort" +/// +/// [profile.release] +/// panic = "abort" +/// ``` +/// +/// # Safety +/// +/// It is crucial to only ever use this function having defined `panic = "abort"`, or else bad +/// things may happen. It's *up to you* to uphold this invariant! +/// +/// If this behaviour is undesirable, use [`replace_with`] or [`replace_with_or_default`]. +/// +/// Equivalent to `replace_with(dest, || process::abort(), f)`. +/// +/// # Example +/// +/// ``` +/// # use replace_with::*; +/// enum States { +/// A(String), +/// B(String), +/// } +/// +/// impl States { +/// fn poll(&mut self) { +/// unsafe { +/// replace_with_or_abort_unchecked(self, |self_| match self_ { +/// States::A(a) => States::B(a), +/// States::B(a) => States::A(a), +/// }); +/// } +/// } +/// } +/// ``` +/// +#[inline] +#[cfg(feature = "panic_abort")] +pub unsafe fn replace_with_or_abort_unchecked T>(dest: &mut T, f: F) { + ptr::write(dest, f(ptr::read(dest))); +} + +/// Temporarily takes ownership of a value at a mutable location, and replace it with a new value +/// based on the old one. Lets the closure return a custom value as well. +/// +/// We move out of the reference temporarily, to apply a closure `f`, returning a new value, which +/// is then placed at the original value's location. +/// +/// This is effectively the same function as [`replace_with`], but it lets the closure return a +/// custom value as well. +/// +/// # An important note +/// +/// On panic (or to be more precise, unwinding) of the closure `f`, `default` will be called to +/// provide a replacement value. `default` should not panic – doing so will constitute a double +/// panic and will most likely abort the process. +/// +/// # Example +/// +/// ``` +/// # use replace_with::*; +/// +/// fn take(option: &mut Option) -> Option { +/// replace_with_and_return(option, || None, |option| (option, None)) +/// } +/// +/// let mut opt = Some(3); +/// assert_eq!(take(&mut opt), Some(3)); +/// assert_eq!(take(&mut opt), None); +/// ``` +#[inline] +pub fn replace_with_and_return T, F: FnOnce(T) -> (U, T)>( + dest: &mut T, default: D, f: F, +) -> U { + unsafe { + let old = ptr::read(dest); + let (res, new) = on_unwind(move || f(old), || ptr::write(dest, default())); + ptr::write(dest, new); + res + } +} + +/// Temporarily takes ownership of a value at a mutable location, and replace it with a new value +/// based on the old one. Replaces with [`Default::default()`] on panic. +/// Lets the closure return a custom value as well. +/// +/// We move out of the reference temporarily, to apply a closure `f`, returning a new value, which +/// is then placed at the original value's location. +/// +/// This is effectively the same function as [`replace_with_or_default`], but it lets the closure +/// return a custom value as well. +/// +/// # An important note +/// +/// On panic (or to be more precise, unwinding) of the closure `f`, `T::default()` will be called to +/// provide a replacement value. `T::default()` should not panic – doing so will constitute a double +/// panic and will most likely abort the process. +/// +/// Equivalent to `replace_with_and_return(dest, T::default, f)`. +/// +/// # Example +/// +/// ``` +/// # use replace_with::*; +/// +/// fn take(option: &mut Option) -> Option { +/// replace_with_or_default_and_return(option, |option| (option, None)) +/// } +/// ``` +#[inline] +pub fn replace_with_or_default_and_return (U, T)>( + dest: &mut T, f: F, +) -> U { + replace_with_and_return(dest, T::default, f) +} + +/// Temporarily takes ownership of a value at a mutable location, and replace it with a new value +/// based on the old one. Aborts on panic. Lets the closure return a custom value as well. +/// +/// We move out of the reference temporarily, to apply a closure `f`, returning a new value, which +/// is then placed at the original value's location. +/// +/// This is effectively the same function as [`replace_with_or_abort`], but it lets the closure +/// return a custom value as well. +/// +/// # An important note +/// +/// On panic (or to be more precise, unwinding) of the closure `f`, the process will **abort** to +/// avoid returning control while `dest` is in a potentially invalid state. +/// +/// If this behaviour is undesirable, use [`replace_with_and_return`] or +/// [`replace_with_or_default_and_return`] instead. +/// +/// Equivalent to `replace_with_and_return(dest, || process::abort(), f)`. +/// +/// # Example +/// +/// ``` +/// # use replace_with::*; +/// +/// fn take(option: &mut Option) -> Option { +/// replace_with_or_abort_and_return(option, |option| (option, None)) +/// } +/// ``` +#[inline] +#[cfg(feature = "std")] +pub fn replace_with_or_abort_and_return (U, T)>(dest: &mut T, f: F) -> U { + replace_with_and_return(dest, || std::process::abort(), f) +} + +#[inline] +#[cfg(all(not(feature = "std"), feature = "nightly"))] +pub fn replace_with_or_abort_and_return (U, T)>(dest: &mut T, f: F) -> U { + replace_with_and_return(dest, || unsafe { std::intrinsics::abort() }, f) +} + +/// Temporarily takes ownership of a value at a mutable location, and replace it with a new value +/// based on the old one. Aborts on panic. Lets the closure return a custom value as well. +/// +/// We move out of the reference temporarily, to apply a closure `f`, returning a new value, which +/// is then placed at the original value's location. +/// +/// This is effectively the same function as [`replace_with_or_abort_unchecked`], but it lets the +/// closure return a custom value as well. +/// +/// # An important note +/// +/// On panic (or to be more precise, unwinding) of the closure `f`, the process will **abort** to +/// avoid returning control while `dest` is in a potentially invalid state. +/// +/// Unlike for `replace_with_or_abort()` users of `replace_with_or_abort_unchecked()` are expected +/// to have `features = ["panic_abort", …]` defined in `Cargo.toml` +/// and `panic = "abort"` defined in their profile for it to behave semantically correct: +/// +/// ```toml +/// # Cargo.toml +/// +/// [profile.debug] +/// panic = "abort" +/// +/// [profile.release] +/// panic = "abort" +/// ``` +/// +/// # Safety +/// +/// It is crucial to only ever use this function having defined `panic = "abort"`, or else bad +/// things may happen. It's *up to you* to uphold this invariant! +/// +/// If this behaviour is undesirable, use [`replace_with_and_return`] or +/// [`replace_with_or_default_and_return`]. +/// +/// Equivalent to `replace_with_and_return(dest, || process::abort(), f)`. +/// +/// # Example +/// +/// ``` +/// # use replace_with::*; +/// +/// unsafe fn take(option: &mut Option) -> Option { +/// replace_with_or_abort_and_return_unchecked(option, |option| (option, None)) +/// } +/// ``` +#[inline] +#[cfg(feature = "std")] +#[cfg(feature = "panic_abort")] +pub unsafe fn replace_with_or_abort_and_return_unchecked (U, T)>( + dest: &mut T, f: F, +) -> U { + let (res, new) = f(ptr::read(dest)); + ptr::write(dest, new); + res +} + +#[cfg(test)] +mod test { + // These functions copied from https://github.com/Sgeo/take_mut/blob/1bd70d842c6febcd16ec1fe3a954a84032b89f52/src/lib.rs#L102-L147 + + // Copyright (c) 2016 Sgeo + + // 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. + + use super::*; + + #[test] + fn it_works_recover() { + #[derive(PartialEq, Eq, Debug)] + enum Foo { + A, + B, + }; + impl Drop for Foo { + #[cfg(feature = "std")] + fn drop(&mut self) { + match *self { + Foo::A => println!("Foo::A dropped"), + Foo::B => println!("Foo::B dropped"), + } + } + + #[cfg(not(feature = "std"))] + fn drop(&mut self) { + match *self { + Foo::A | Foo::B => (), + } + } + } + let mut quax = Foo::A; + replace_with( + &mut quax, + || Foo::A, + |f| { + drop(f); + Foo::B + }, + ); + assert_eq!(&quax, &Foo::B); + + let n = replace_with_and_return( + &mut quax, + || Foo::B, + |f| { + drop(f); + (3, Foo::A) + }, + ); + assert_eq!(n, 3); + assert_eq!(&quax, &Foo::A); + } + + #[cfg(all(feature = "std", not(miri)))] // https://github.com/rust-lang/miri/issues/658 + #[test] + fn it_works_recover_panic() { + use std::panic; + + #[derive(PartialEq, Eq, Debug)] + enum Foo { + A, + B, + C, + }; + impl Drop for Foo { + fn drop(&mut self) { + match *self { + Foo::A => println!("Foo::A dropped"), + Foo::B => println!("Foo::B dropped"), + Foo::C => println!("Foo::C dropped"), + } + } + } + let mut quax = Foo::A; + + let res = panic::catch_unwind(panic::AssertUnwindSafe(|| { + replace_with( + &mut quax, + || Foo::C, + |f| { + drop(f); + panic!("panic"); + #[allow(unreachable_code)] + Foo::B + }, + ); + })); + + assert!(res.is_err()); + assert_eq!(&quax, &Foo::C); + } +} diff --git a/toolkit/library/rust/shared/Cargo.toml b/toolkit/library/rust/shared/Cargo.toml index 3490a0a383a9..17a9250ef693 100644 --- a/toolkit/library/rust/shared/Cargo.toml +++ b/toolkit/library/rust/shared/Cargo.toml @@ -66,6 +66,8 @@ rusqlite = { version = "0.24.1", features = ["modern_sqlite", "in_gecko"] } fluent = { version = "0.15", features = ["fluent-pseudo"] } fluent-ffi = { path = "../../../../intl/l10n/rust/fluent-ffi" } l10nregistry-ffi = { path = "../../../../intl/l10n/rust/l10nregistry-ffi" } +l10nregistry = { git = "https://github.com/zbraniecki/l10nregistry-rs", rev = "92d8fbfbbbdffa2047ce01a935a389eb11031f69" } +fluent-fallback = "0.5" processtools = { path = "../../../components/processtools" } qcms = { path = "../../../../gfx/qcms", features = ["c_bindings", "neon"], default-features = false } diff --git a/toolkit/library/rust/shared/lib.rs b/toolkit/library/rust/shared/lib.rs index 91290749378d..a7ba9a877fe4 100644 --- a/toolkit/library/rust/shared/lib.rs +++ b/toolkit/library/rust/shared/lib.rs @@ -74,6 +74,9 @@ extern crate fluent_langneg_ffi; extern crate fluent; extern crate fluent_ffi; +extern crate fluent_fallback; +extern crate l10nregistry; + #[cfg(not(target_os = "android"))] extern crate viaduct;