Bug 1672317 - [l10nfilesource] part2: Vendor in l10nregistry-rs and fluent-fallback. r=dminor

Depends on D103184

Differential Revision: https://phabricator.services.mozilla.com/D102102
This commit is contained in:
Zibi Braniecki 2021-07-29 21:30:25 +00:00
parent bcff9a7e2d
commit e0ee86ae33
139 changed files with 15020 additions and 114 deletions

View File

@ -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"

111
Cargo.lock generated
View File

@ -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"

View File

@ -5,9 +5,12 @@ authors = ["Zibi Braniecki <zibi@braniecki.net>"]
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"

View File

@ -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"}

51
third_party/rust/async-trait/Cargo.toml vendored Normal file
View File

@ -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 <dtolnay@gmail.com>"]
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"]

View File

@ -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.

View File

@ -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.

262
third_party/rust/async-trait/README.md vendored Normal file
View File

@ -0,0 +1,262 @@
Async trait methods
===================
[<img alt="github" src="https://img.shields.io/badge/github-dtolnay/async--trait-8da0cb?style=for-the-badge&labelColor=555555&logo=github" height="20">](https://github.com/dtolnay/async-trait)
[<img alt="crates.io" src="https://img.shields.io/crates/v/async-trait.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/async-trait)
[<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-async--trait-66c2a5?style=for-the-badge&labelColor=555555&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K" height="20">](https://docs.rs/async-trait)
[<img alt="build status" src="https://img.shields.io/github/workflow/status/dtolnay/async-trait/CI/master?style=for-the-badge" height="20">](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/
<br>
## 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;
}
}
```
<br>
## 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.
- &#128077;&ensp;Self by value, by reference, by mut reference, or no self;
- &#128077;&ensp;Any number of arguments, any return value;
- &#128077;&ensp;Generic type parameters and lifetime parameters;
- &#128077;&ensp;Associated types;
- &#128077;&ensp;Having async and non-async functions in the same trait;
- &#128077;&ensp;Default implementations provided by the trait;
- &#128077;&ensp;Elided lifetimes;
- &#128077;&ensp;Dyn-capable traits.
<br>
## Explanation
Async fns get transformed into methods that return `Pin<Box<dyn Future + Send +
'async_trait>>` 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<Box<dyn std::future::Future<Output = ()> + Send + 'async_trait>>
where
Self: Sync + 'async_trait,
{
async fn run(_self: &AutoplayingVideo) {
/* the original method body */
}
Box::pin(run(self))
}
}
```
<br>
## 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.
<br>
## 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<'_>) {}
}
```
<br>
## 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;
```
<br>
#### License
<sup>
Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.
</sup>
<br>
<sub>
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.
</sub>

View File

@ -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<Self> {
match try_parse(input) {
Ok(args) if input.is_empty() => Ok(args),
_ => Err(error()),
}
}
}
fn try_parse(input: ParseStream) -> Result<Args> {
if input.peek(Token![?]) {
input.parse::<Token![?]>()?;
input.parse::<kw::Send>()?;
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)
}

View File

@ -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<Item = &'a GenericParam> {
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(&param.lifetime)
} else {
false
}
})
}
}
type Supertraits = Punctuated<TypeParamBound, Token![+]>;
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<T>(&self, x: &T) -> Ret;
//
// Output:
// fn f<'life0, 'life1, 'async_trait, T>(
// &'life0 self,
// x: &'life1 T,
// ) -> Pin<Box<dyn Future<Output = Ret> + 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 = &param.ident;
where_clause
.predicates
.push(parse_quote!(#param: 'async_trait));
}
GenericParam::Lifetime(param) => {
let param = &param.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<Box<
dyn ::core::future::Future<Output = #ret> + #bounds
>>
};
}
// Input:
// async fn f<T>(&self, x: &T) -> Ret {
// self + x
// }
//
// Output:
// async fn f<T, AsyncTrait>(_self: &AsyncTrait, x: &T) -> Ret {
// _self + x
// }
// Box::pin(async_trait_method::<T, Self>(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::<Vec<_>>();
let mut self_bound = None::<TypeParamBound>;
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 <AsyncTrait>::Output: Send` which is illegal syntax because
// `where<T>` 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
}

330
third_party/rust/async-trait/src/lib.rs vendored Normal file
View File

@ -0,0 +1,330 @@
//! [![github]](https://github.com/dtolnay/async-trait)&ensp;[![crates-io]](https://crates.io/crates/async-trait)&ensp;[![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
//!
//! <br>
//!
//! <h5>Type erasure for async trait methods</h5>
//!
//! 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/
//!
//! <br>
//!
//! # 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) {}
//! # }
//! ```
//!
//! <br><br>
//!
//! # 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.
//!
//! > &#9745;&emsp;Self by value, by reference, by mut reference, or no self;<br>
//! > &#9745;&emsp;Any number of arguments, any return value;<br>
//! > &#9745;&emsp;Generic type parameters and lifetime parameters;<br>
//! > &#9745;&emsp;Associated types;<br>
//! > &#9745;&emsp;Having async and non-async functions in the same trait;<br>
//! > &#9745;&emsp;Default implementations provided by the trait;<br>
//! > &#9745;&emsp;Elided lifetimes;<br>
//! > &#9745;&emsp;Dyn-capable traits.<br>
//!
//! <br>
//!
//! # Explanation
//!
//! Async fns get transformed into methods that return `Pin<Box<dyn Future +
//! Send + 'async>>` 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<Box<dyn core::future::Future<Output = ()> + Send + 'async>>
//! where
//! Self: Sync + 'async,
//! {
//! async fn run(_self: &AutoplayingVideo) {
//! /* the original method body */
//! }
//!
//! Box::pin(run(self))
//! }
//! }
//! # };
//! ```
//!
//! <br><br>
//!
//! # 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.
//!
//! <br>
//!
//! # 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<'_>) {}
//! }
//! ```
//!
//! <br><br>
//!
//! # 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))
}

View File

@ -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<Lifetime>,
pub explicit: Vec<Lifetime>,
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<Lifetime>) {
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);
}
}

View File

@ -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<Self> {
let attrs = input.call(Attribute::parse_outer)?;
let mut lookahead = input.lookahead1();
if lookahead.peek(Token![unsafe]) {
let ahead = input.fork();
ahead.parse::<Token![unsafe]>()?;
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())
}
}
}

View File

@ -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<Path>,
}
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<QSelf>, 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<QSelf>, 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<QSelf>, 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` -> `<Receiver>::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` -> `<Receiver>::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
}

View File

@ -0,0 +1,22 @@
use proc_macro2::{Span, TokenStream};
use quote::ToTokens;
use syn::parse::Parse;
pub(crate) fn respan<T>(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()
}

View File

@ -0,0 +1,6 @@
#[rustversion::attr(not(nightly), ignore)]
#[test]
fn ui() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/ui/*.rs");
}

View File

@ -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<F: Future>(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"),
}
}

1079
third_party/rust/async-trait/tests/test.rs vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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() {}

View File

@ -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)]
| ^^^^^^^^^^^^^^^^^^

View File

@ -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() {}

View File

@ -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

View File

@ -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() {}

View File

@ -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: `{ <body> }`

View File

@ -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() {}

View File

@ -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)]
| ^^^^^^^^^^^^^^^

View File

@ -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() {}

View File

@ -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;
| ^^^^^^^

View File

@ -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() {}

View File

@ -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<Output = ()> + Send`

View File

@ -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() {}

View File

@ -0,0 +1,5 @@
error: Self type of this impl is unsupported in expression position
--> $DIR/unsupported-self.rs:11:17
|
11 | let _ = Self;
| ^^^^

View File

@ -0,0 +1 @@
{"files":{"Cargo.toml":"3ab88cdacffa2756abe4460dda1ef403b304e79f814a2ec71b9c9a013dce2bf6","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"ea4a2543160e8e33d4e2d2b40339c90e0ab2960cfa59a65264b66782d7f9e35f","README.md":"382da770bccf4ebe07dded523a9ab5e4daead902edbbbffd20740404e73ee3d5","src/lib.rs":"01dc7e2a981fc95825584916d320fd60232b07fc88761126c89b12da3e3f20e5"},"package":"bb7bdea464ae038f09197b82430b921c53619fc8d2bcaf7b151013b3ca008017"}

24
third_party/rust/chunky-vec/Cargo.toml vendored Normal file
View File

@ -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 <dan.glastonbury@gmail.com>"]
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]

View File

@ -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.

27
third_party/rust/chunky-vec/LICENSE-MIT vendored Normal file
View File

@ -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.

6
third_party/rust/chunky-vec/README.md vendored Normal file
View File

@ -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.

343
third_party/rust/chunky-vec/src/lib.rs vendored Normal file
View File

@ -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<T> {
/// The elements of this chunk.
elements: Vec<T>,
}
impl<T> Chunk<T> {
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<T> {
self.elements.iter()
}
/// Returns an iterator over the elements of the chunk.
pub fn iter_mut(&mut self) -> slice::IterMut<T> {
self.elements.iter_mut()
}
}
impl<T> Index<usize> for Chunk<T> {
type Output = T;
fn index(&self, index: usize) -> &Self::Output {
self.get(index).expect("index out of bounds")
}
}
impl<T> IndexMut<usize> for Chunk<T> {
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<T> {
/// The chunks holding elements
chunks: Vec<Chunk<T>>,
}
impl<T> Default for ChunkyVec<T> {
fn default() -> Self {
Self {
chunks: Vec::default(),
}
}
}
impl<T> Unpin for ChunkyVec<T> {}
impl<T> ChunkyVec<T> {
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<T> {
Iter::new(self)
}
/// Returns an iterator that yields exclusive reference to the elements of the bucket vector.
pub fn iter_mut(&mut self) -> IterMut<T> {
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<T> Index<usize> for ChunkyVec<T> {
type Output = T;
fn index(&self, index: usize) -> &Self::Output {
self.get(index).expect("index out of bounds")
}
}
impl<T> IndexMut<usize> for ChunkyVec<T> {
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<T>>,
/// Forward iterator for `next`.
iter: Option<slice::Iter<'a, T>>,
/// 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<T>) -> 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<Self::Item> {
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<usize>) {
(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<T>>,
/// Forward iterator for `next`.
iter: Option<slice::IterMut<'a, T>>,
/// 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<T>) -> 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<Self::Item> {
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<usize>) {
(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<T> {
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<T> {
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::<usize>::default();
for i in &v {
println!("{:?}", i);
}
}
#[test]
fn iterate_multiple_chunks() {
let mut v = ChunkyVec::<usize>::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::<usize>::default();
for i in 0..33 {
v.push(i);
}
assert_eq!(v.get(32), Some(&32));
assert_eq!(v[32], 32);
}
}

View File

@ -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"}
{"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"}

View File

@ -13,7 +13,7 @@
[package]
edition = "2018"
name = "fluent-bundle"
version = "0.15.0"
version = "0.15.1"
authors = ["Zibi Braniecki <gandalf@mozilla.com>", "Staś Małolepszy <stas@mozilla.com>"]
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"

View File

@ -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

View File

@ -1,4 +1,4 @@
mod errors;
pub mod errors;
mod expression;
mod inline_expression;
mod pattern;

View File

@ -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}')?;
}

View File

@ -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"}

View File

@ -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.

468
third_party/rust/fluent-fallback/Cargo.lock generated vendored Normal file
View File

@ -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"

View File

@ -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 <gandalf@mozilla.com>", "Staś Małolepszy <stas@mozilla.com>"]
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"]

View File

@ -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.

View File

@ -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.

View File

@ -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)

View File

@ -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.
}

View File

@ -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.
}

View File

@ -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<Vec<LanguageIdentifier>> {
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<LanguageIdentifier> {
let default_locale = langid!("en-US");
let available = get_available_locales().expect("Retrieving available locales failed.");
let requested: Vec<LanguageIdentifier> = 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<String> = env::args().collect();
let app_locales: Vec<LanguageIdentifier> = 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: <Vec<LanguageIdentifier> as IntoIterator>::IntoIter,
res_ids: Vec<String>,
}
impl Iterator for BundleIter {
type Item = FluentBundleResult<FluentResource>;
fn next(&mut self) -> Option<Self::Item> {
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<FluentResource>;
fn poll_next(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
todo!()
}
}
impl BundleGenerator for Bundles {
type Resource = FluentResource;
type LocalesIter = std::vec::IntoIter<LanguageIdentifier>;
type Iter = BundleIter;
type Stream = BundleIter;
fn bundles_iter(&self, locales: Self::LocalesIter, res_ids: Vec<String>) -> Self::Iter {
BundleIter {
res_path_scheme: self.res_path_scheme.to_string_lossy().to_string(),
locales,
res_ids,
}
}
}

View File

@ -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<G>
where
G: BundleGenerator,
{
Iter(Cache<G::Iter, G::Resource>),
Stream(AsyncCache<G::Stream, G::Resource>),
}
pub struct Bundles<G>(BundlesInner<G>)
where
G: BundleGenerator;
impl<G> Bundles<G>
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<G> Bundles<G>
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<G> Bundles<G>
where
G: BundleGenerator,
{
pub fn new<P>(sync: bool, res_ids: Vec<String>, generator: &G, provider: &P) -> Self
where
G: BundleGenerator<LocalesIter = P::Iter>,
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<LocalizationError>,
) -> Option<Cow<'l, str>> {
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<LocalizationError>,
) -> Vec<Option<Cow<'l, str>>> {
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<LocalizationError>,
) -> Vec<Option<L10nMessage<'l>>> {
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<LocalizationError>,
) -> Result<Option<Cow<'l, str>>, 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<LocalizationError>,
) -> Result<Vec<Option<Cow<'l, str>>>, 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<LocalizationError>,
) -> Result<Vec<Option<L10nMessage<'l>>>, 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<G> Bundles<G>
where
G: BundleGenerator,
{
fn format_value_from_iter<'l>(
cache: &'l Cache<G::Iter, G::Resource>,
id: &'l str,
args: Option<&'l FluentArgs>,
errors: &mut Vec<LocalizationError>,
) -> Option<Cow<'l, str>> {
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<G::Stream, G::Resource>,
id: &'l str,
args: Option<&'l FluentArgs<'_>>,
errors: &mut Vec<LocalizationError>,
) -> Option<Cow<'l, str>> {
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<G::Stream, G::Resource>,
keys: &'l [L10nKey<'l>],
errors: &mut Vec<LocalizationError>,
) -> Vec<Option<L10nMessage<'l>>> {
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<G::Stream, G::Resource>,
keys: &'l [L10nKey<'l>],
errors: &mut Vec<LocalizationError>,
) -> Vec<Option<Cow<'l, str>>> {
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<G::Resource>,
key: &'l L10nKey,
format_errors: &mut Vec<FluentError>,
) -> Option<L10nMessage<'l>> {
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<G::Iter, G::Resource>,
keys: &'l [L10nKey<'l>],
errors: &mut Vec<LocalizationError>,
) -> Vec<Option<L10nMessage<'l>>> {
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<G::Iter, G::Resource>,
keys: &'l [L10nKey<'l>],
errors: &mut Vec<LocalizationError>,
) -> Vec<Option<Cow<'l, str>>> {
let mut bundle_iter = cache.into_iter();
format_values_from_inner!(bundle_iter.next(), keys, errors);
}
}

View File

@ -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<I, R>
where
I: Iterator,
{
iter: RefCell<I>,
items: UnsafeCell<ChunkyVec<I::Item>>,
res: std::marker::PhantomData<R>,
}
impl<I, R> Cache<I, R>
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<I, R> Cache<I, R>
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<I, R>,
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<Self::Item> {
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<I, R>
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<S, R>
where
S: Stream,
{
stream: PinCell<S>,
items: UnsafeCell<ChunkyVec<S::Item>>,
// TODO: Should probably be an SmallVec<[Waker; 1]> or something? I guess
// multiple pending wakes are not really all that common.
pending_wakes: RefCell<Vec<Waker>>,
res: std::marker::PhantomData<R>,
}
impl<S, R> AsyncCache<S, R>
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<Option<&S::Item>> {
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<S, R> AsyncCache<S, R>
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<S, R> AsyncCache<S, R>
where
S: Stream,
{
// Helper function that gets the next value from wrapped stream.
fn poll_next_item(&self, cx: &mut Context<'_>) -> Poll<Option<S::Item>> {
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<S, R>,
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<Option<Self::Item>> {
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()
}
}
}
}

View File

@ -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<RefCell<Vec<LanguageIdentifier>>>,
/// }
///
/// impl Env {
/// pub fn new(locales: Vec<LanguageIdentifier>) -> Self {
/// Self { locales: Rc::new(RefCell::new(locales)) }
/// }
///
/// pub fn set_locales(&mut self, new_locales: Vec<LanguageIdentifier>) {
/// let mut locales = self.locales.borrow_mut();
/// locales.clear();
/// locales.extend(new_locales);
/// }
/// }
///
/// impl LocalesProvider for Env {
/// type Iter = <Vec<LanguageIdentifier> 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<Item = LanguageIdentifier>;
fn locales(&self) -> Self::Iter;
}
impl LocalesProvider for Vec<LanguageIdentifier> {
type Iter = <Vec<LanguageIdentifier> as IntoIterator>::IntoIter;
fn locales(&self) -> Self::Iter {
self.clone().into_iter()
}
}

View File

@ -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<FluentError>,
},
MissingMessage {
id: String,
locale: Option<LanguageIdentifier>,
},
MissingValue {
id: String,
locale: Option<LanguageIdentifier>,
},
SyncRequestInAsyncMode,
}
impl From<FluentError> 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<String> = 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 {}

View File

@ -0,0 +1,30 @@
use fluent_bundle::{FluentBundle, FluentError, FluentResource};
use futures::Stream;
use std::borrow::Borrow;
use unic_langid::LanguageIdentifier;
pub type FluentBundleResult<R> = Result<FluentBundle<R>, (FluentBundle<R>, Vec<FluentError>)>;
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<FluentResource>;
type LocalesIter: Iterator<Item = LanguageIdentifier>;
type Iter: Iterator<Item = FluentBundleResult<Self::Resource>>;
type Stream: Stream<Item = FluentBundleResult<Self::Resource>>;
fn bundles_iter(&self, _locales: Self::LocalesIter, _res_ids: Vec<String>) -> Self::Iter {
unimplemented!();
}
fn bundles_stream(&self, _locales: Self::LocalesIter, _res_ids: Vec<String>) -> Self::Stream {
unimplemented!();
}
}

View File

@ -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<LagnuageIdentifier>`](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;

View File

@ -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<G, P>
where
G: BundleGenerator<LocalesIter = P::Iter>,
P: LocalesProvider,
{
bundles: OnceCell<Rc<Bundles<G>>>,
generator: G,
provider: P,
sync: bool,
res_ids: Vec<String>,
}
impl<G, P> Localization<G, P>
where
G: BundleGenerator<LocalesIter = P::Iter> + Default,
P: LocalesProvider + Default,
{
pub fn new(res_ids: Vec<String>, sync: bool) -> Self {
Self {
bundles: OnceCell::new(),
generator: G::default(),
provider: P::default(),
sync,
res_ids,
}
}
}
impl<G, P> Localization<G, P>
where
G: BundleGenerator<LocalesIter = P::Iter>,
P: LocalesProvider,
{
pub fn with_env(res_ids: Vec<String>, 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<String>) {
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<String>) -> 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<G, P> Localization<G, P>
where
G: BundleGenerator<LocalesIter = P::Iter>,
G::Iter: BundleIterator,
P: LocalesProvider,
{
pub fn prefetch_sync(&mut self) {
let bundles = self.bundles();
bundles.prefetch_sync();
}
}
impl<G, P> Localization<G, P>
where
G: BundleGenerator<LocalesIter = P::Iter>,
G::Stream: BundleStream,
P: LocalesProvider,
{
pub async fn prefetch_async(&mut self) {
let bundles = self.bundles();
bundles.prefetch_async().await
}
}
impl<G, P> Localization<G, P>
where
G: BundleGenerator<LocalesIter = P::Iter>,
P: LocalesProvider,
{
pub fn bundles(&self) -> &Rc<Bundles<G>> {
self.bundles.get_or_init(|| {
Rc::new(Bundles::new(
self.sync,
self.res_ids.clone(),
&self.generator,
&self.provider,
))
})
}
}

View File

@ -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.

View File

@ -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<T: ?Sized> {
inner: RefCell<T>,
}
impl<T> PinCell<T> {
/// Creates a new `PinCell` containing `value`.
pub const fn new(value: T) -> PinCell<T> {
PinCell {
inner: RefCell::new(value),
}
}
}
impl<T: ?Sized> PinCell<T> {
/// 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<PinMut<'a, T>, BorrowMutError> {
let ref_mut: RefMut<'a, T> = Pin::get_ref(self).inner.try_borrow_mut()?;
// this is a pin projection from Pin<&PinCell<T>> to Pin<RefMut<T>>
// projecting is safe because:
//
// - for<T: ?Sized> (PinCell<T>: Unpin) imples (RefMut<T>: Unpin)
// holds true
// - PinCell does not implement Drop
//
// see discussion on tracking issue #49150 about pin projection
// invariants
let pin_ref_mut: Pin<RefMut<'a, T>> = unsafe { Pin::new_unchecked(ref_mut) };
Ok(PinMut { inner: pin_ref_mut })
}
}
impl<T> From<T> for PinCell<T> {
fn from(value: T) -> PinCell<T> {
PinCell::new(value)
}
}
impl<T> From<RefCell<T>> for PinCell<T> {
fn from(cell: RefCell<T>) -> PinCell<T> {
PinCell { inner: cell }
}
}
impl<T> From<PinCell<T>> for RefCell<T> {
fn from(input: PinCell<T>) -> Self {
input.inner
}
}
// TODO CoerceUnsized

View File

@ -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<T>`.
pub struct PinMut<'a, T: ?Sized> {
pub(crate) inner: Pin<RefMut<'a, T>>,
}
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<U, F>(orig: PinMut<'a, T>, f: F) -> PinMut<'a, U> where
F: FnOnce(Pin<&mut T>) -> Pin<&mut U>,
{
panic!()
}
pub fn map_split<U, V, F>(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 {
<T as fmt::Display>::fmt(&**self, f)
}
}
// TODO CoerceUnsized

View File

@ -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<T>`.
pub struct PinRef<'a, T: ?Sized> {
pub(crate) inner: Pin<Ref<'a, T>>,
}
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<U, F>(orig: PinRef<'a, T>, f: F) -> PinRef<'a, U> where
F: FnOnce(Pin<&T>) -> Pin<&U>,
{
panic!()
}
pub fn map_split<U, V, F>(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 {
<T as fmt::Display>::fmt(&**self, f)
}
}
// TODO CoerceUnsized

View File

@ -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<FluentArgs<'l>>,
}
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<Cow<'l, str>>,
pub attributes: Vec<L10nAttribute<'l>>,
}

View File

@ -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<Vec<LanguageIdentifier>>,
}
impl InnerLocales {
pub fn insert(&self, index: usize, element: LanguageIdentifier) {
self.locales.borrow_mut().insert(index, element);
}
}
#[derive(Clone)]
struct Locales {
inner: Rc<InnerLocales>,
}
impl Locales {
pub fn new(locales: Vec<LanguageIdentifier>) -> 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 = <Vec<LanguageIdentifier> 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: <Vec<LanguageIdentifier> as IntoIterator>::IntoIter,
res_ids: Vec<String>,
}
impl Iterator for BundleIter {
type Item = FluentBundleResult<FluentResource>;
fn next(&mut self) -> Option<Self::Item> {
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<FluentResource>;
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
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<LanguageIdentifier>;
type Iter = BundleIter;
type Stream = BundleIter;
fn bundles_iter(&self, locales: Self::LocalesIter, res_ids: Vec<String>) -> Self::Iter {
BundleIter { locales, res_ids }
}
fn bundles_stream(&self, locales: Self::LocalesIter, res_ids: Vec<String>) -> Self::Stream {
BundleIter { locales, res_ids }
}
}
#[test]
fn localization_format() {
let resource_ids: Vec<String> = 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<String> = 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<String> = 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<String> = 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<String> = 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<String> = 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<String> = 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<String> = 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;
}

View File

@ -0,0 +1,4 @@
hello-world = Hello World [en]
message-1 = Message 1 Value [en]
.attr1 = Message 1 Attribute [en]

View File

@ -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]

View File

@ -0,0 +1,4 @@
hello-world = Hello World [pl]
message-1 = Message 1 Value [pl]
.attr1 = Message 1 Attribute [pl]

View File

@ -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]

View File

@ -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"}

View File

@ -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 <alex@alexcrichton.com>"]
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"]

View File

@ -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.

View File

@ -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.

View File

@ -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<Self::Output> {
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<Self::Output> {
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::<Waker>(10_000);
struct Yield {
rem: usize,
tx: mpsc::SyncSender<Waker>,
}
impl Unpin for Yield {}
impl Future for Yield {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
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);
});
}

View File

@ -0,0 +1,80 @@
use std::cell::Cell;
use std::fmt;
thread_local!(static ENTERED: Cell<bool> = 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<Enter, EnterError> {
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);
});
}
}

View File

@ -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};

View File

@ -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<LocalFutureObj<'static, ()>>,
incoming: Rc<Incoming>,
}
/// A handle to a [`LocalPool`](LocalPool) that implements
/// [`Spawn`](futures_task::Spawn).
#[derive(Clone, Debug)]
pub struct LocalSpawner {
incoming: Weak<Incoming>,
}
type Incoming = RefCell<Vec<LocalFutureObj<'static, ()>>>;
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<ThreadNotify> = Arc::new(ThreadNotify {
thread: thread::current(),
unparked: AtomicBool::new(false),
});
}
impl ArcWake for ThreadNotify {
fn wake_by_ref(arc_self: &Arc<Self>) {
// 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<T, F: FnMut(&mut Context<'_>) -> Poll<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);
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, F: FnMut(&mut Context<'_>) -> 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<F: Future>(&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<Option<()>> {
// 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: Future>(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<S: Stream + Unpin>(stream: S) -> BlockingStream<S> {
BlockingStream { stream }
}
/// An iterator which blocks on values from a stream until they become available.
#[derive(Debug)]
pub struct BlockingStream<S: Stream + Unpin> {
stream: S,
}
impl<S: Stream + Unpin> Deref for BlockingStream<S> {
type Target = S;
fn deref(&self) -> &Self::Target {
&self.stream
}
}
impl<S: Stream + Unpin> DerefMut for BlockingStream<S> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.stream
}
}
impl<S: Stream + Unpin> BlockingStream<S> {
/// Convert this `BlockingStream` into the inner `Stream` type.
pub fn into_inner(self) -> S {
self.stream
}
}
impl<S: Stream + Unpin> Iterator for BlockingStream<S> {
type Item = S::Item;
fn next(&mut self) -> Option<Self::Item> {
LocalPool::new().run_until(self.stream.next())
}
fn size_hint(&self) -> (usize, Option<usize>) {
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())
}
}
}

View File

@ -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<PoolState>,
}
/// 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<String>,
after_start: Option<Arc<dyn Fn(usize) + Send + Sync>>,
before_stop: Option<Arc<dyn Fn(usize) + Send + Sync>>,
}
trait AssertSendSync: Send + Sync {}
impl AssertSendSync for ThreadPool {}
struct PoolState {
tx: Mutex<mpsc::Sender<Message>>,
rx: Mutex<mpsc::Receiver<Message>>,
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<Self, io::Error> {
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<Fut>(&self, future: Fut)
where
Fut: Future<Output = ()> + 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<Arc<dyn Fn(usize) + Send + Sync>>,
before_stop: Option<Arc<dyn Fn(usize) + Send + Sync>>,
) {
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<S: Into<String>>(&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<F>(&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<F>(&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<ThreadPool, io::Error> {
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<WakeHandle>,
}
struct WakeHandle {
mutex: UnparkMutex<Task>,
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<Self>) {
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);
}
}

View File

@ -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<D> {
// The state of task execution (state machine described below)
status: AtomicUsize,
// The actual task data, accessible only in the POLLING state
inner: UnsafeCell<Option<D>>,
}
// `UnparkMutex<D>` functions in many ways like a `Mutex<D>`, except that on
// acquisition failure, the current lock holder performs the desired work --
// re-polling.
//
// As such, these impls mirror those for `Mutex<D>`. In particular, a reference
// to `UnparkMutex` can be used to gain `&mut` access to the inner data, which
// must therefore be `Send`.
unsafe impl<D: Send> Send for UnparkMutex<D> {}
unsafe impl<D: Send> Sync for UnparkMutex<D> {}
// 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<D> UnparkMutex<D> {
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<D, ()> {
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);
}
}

View File

@ -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<Cell<Option<Waker>>> = 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<RefCell<[i32; 2]>>,
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)
}

View File

@ -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"}

View File

@ -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 <cramertj@google.com>", "Taiki Endo <te316e89@gmail.com>"]
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]

View File

@ -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.

View File

@ -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.

28
third_party/rust/futures-macro/build.rs vendored Normal file
View File

@ -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");
}

View File

@ -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()
}

View File

@ -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<Expr>,
}
impl Parse for Join {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let mut join = Self::default();
while !input.is_empty() {
join.fut_exprs.push(input.parse::<Expr>()?);
if !input.is_empty() {
input.parse::<Token![,]>()?;
}
}
Ok(join)
}
}
fn bind_futures(fut_exprs: Vec<Expr>, span: Span) -> (Vec<TokenStream2>, Vec<Ident>) {
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
} })
}

View File

@ -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)
}

View File

@ -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<Expr>,
default: Option<Expr>,
normal_fut_exprs: Vec<Expr>,
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<Self> {
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::<kw::complete>()?;
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::<Ident>()?;
CaseKind::Default
} else {
// `<pat> = <expr>`
let pat = input.parse()?;
input.parse::<Token![=]>()?;
let expr = input.parse()?;
CaseKind::Normal(pat, expr)
};
// `=> <expr>`
input.parse::<Token![=>]>()?;
let expr = input.parse::<Expr>()?;
// 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::<Option<Token![,]>>()?;
} else {
input.parse::<Token![,]>()?;
}
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<Ident>, syn::ItemEnum) {
// "_0", "_1", "_2"
let variant_names: Vec<Ident> =
(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<Poll<__PrivResult<...>>`
// 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
} })
}

View File

@ -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}

View File

@ -0,0 +1,63 @@
[package]
name = "l10nregistry"
version = "0.2.0"
authors = ["Zibi Braniecki <gandalf@mozilla.com>"]
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"]

View File

@ -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.

View File

@ -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.

View File

@ -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<FluentArgs>)> = 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<L10nKey> = 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);

View File

@ -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<LanguageIdentifier> = 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);

View File

@ -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<Vec<bool>>,
}
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::Output> {
self.0.into()
}
}
pub type ResourceSetStream = Collect<FuturesOrdered<SingleTestResult>, Vec<bool>>;
pub struct TestResult(ResourceSetStream);
impl std::marker::Unpin for TestResult {}
impl Future for TestResult {
type Output = Vec<bool>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
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::<Vec<_>>();
TestResult(futures.into_iter().collect::<FuturesOrdered<_>>().collect())
}
}
struct TestStream<'t> {
solver: ParallelProblemSolver<MockTester>,
tester: &'t MockTester,
}
impl<'t> TestStream<'t> {
pub fn new(solver: ParallelProblemSolver<MockTester>, tester: &'t MockTester) -> Self {
Self { solver, tester }
}
}
impl<'t> futures::stream::Stream for TestStream<'t> {
type Item = Vec<usize>;
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
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);

View File

@ -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<S>(input: &[S]) -> Vec<LanguageIdentifier>
where
S: AsRef<str>,
{
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<LanguageIdentifier> = 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);

View File

@ -0,0 +1,5 @@
use crate::errors::L10nRegistryError;
pub trait ErrorReporter {
fn report_errors(&self, errors: Vec<L10nRegistryError>);
}

View File

@ -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 {}

Some files were not shown because too many files have changed in this diff Show More