diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:44:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:44:51 +0000 |
commit | 9e3c08db40b8916968b9f30096c7be3f00ce9647 (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /third_party/rust/semver | |
parent | Initial commit. (diff) | |
download | thunderbird-9e3c08db40b8916968b9f30096c7be3f00ce9647.tar.xz thunderbird-9e3c08db40b8916968b9f30096c7be3f00ce9647.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/semver')
22 files changed, 3395 insertions, 0 deletions
diff --git a/third_party/rust/semver/.cargo-checksum.json b/third_party/rust/semver/.cargo-checksum.json new file mode 100644 index 0000000000..3e0993c1aa --- /dev/null +++ b/third_party/rust/semver/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"84eaac27f969839e684c9cdf124748de0a8f0b0876a1eacc31cfaa105f35540f","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3","README.md":"de1a03443ab8f147676199856a975ec00f3f7334fc5d5d5e056ec8f3fcb61dd5","benches/parse.rs":"6531f66f80ce2fc83878f9bf84f94c42e96f1e709466f2b88be8d95a3cec1511","build.rs":"9a3d42e37b665745044b5d91c6e02dd458152e336a7013654972f4a1a0b562d9","src/backport.rs":"66db55d15d0e2808bffe4cde7cd1d99bda999b26cbe40bb6b5e43b94f9b631d2","src/display.rs":"9ba42f7a6579aa9c7dd72f2380036f5c9664592f3eacd09ea25cef291a3e64e5","src/error.rs":"3bb489f4a29f38d93370e64ae8d6e4e9b451a055cd7d392b6aeacab7eb3e1953","src/eval.rs":"b7e7ec976051b9f87ddf5cfdbaad64654d98d86ae0763f7d88b14eeaeac6013c","src/identifier.rs":"459725383cbd0e2d769aa947decd1f031bdc8732339783ad24eb2b44f0f5d040","src/impls.rs":"79b5a2ac6ca3d4cb46adfb1494756079f53bef780dd81c3a8d3adf86f91395c8","src/lib.rs":"cc912c719047aa679429069a26679f681741b91ff66d847f60ddc519262d588c","src/parse.rs":"ffbb84081f0f66ec47b752a1e32f1bea5f206ca84f464b99d0497451305a92f8","src/serde.rs":"e2a9b9dc3cd2cccc250eaffad049de418ef791bf8c4a34111a48f068353e0a37","tests/node/mod.rs":"2710d9b8daace2038b66db0f8f4cc522dee938e7cbc42d7739c31995343c32f4","tests/test_autotrait.rs":"070500c32ceee14a8a0110c04a01f98278b24614a0aec8c382dcea3da0343f58","tests/test_identifier.rs":"6c3da46c73df210527b60f1069131b15e2c65eb7b5d11793940d00cf66812f4d","tests/test_version.rs":"09e37c3df162205acf3683d1c760a6001e34e1c709fd4a1a265d82450e340003","tests/test_version_req.rs":"b6eea0258cc3b6d567a9f6c42693a97316345083495236c47e85374fd45f7cf0","tests/util/mod.rs":"db61c2cd86af864d8be4f2a3d5f25c86d7712201cc6ab47b715facf5f7f275b7"},"package":"58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a"}
\ No newline at end of file diff --git a/third_party/rust/semver/Cargo.toml b/third_party/rust/semver/Cargo.toml new file mode 100644 index 0000000000..e0bfea20f0 --- /dev/null +++ b/third_party/rust/semver/Cargo.toml @@ -0,0 +1,46 @@ +# 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 are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2018" +rust-version = "1.31" +name = "semver" +version = "1.0.16" +authors = ["David Tolnay <dtolnay@gmail.com>"] +description = "Parser and evaluator for Cargo's flavor of Semantic Versioning" +documentation = "https://docs.rs/semver" +readme = "README.md" +keywords = ["cargo"] +categories = [ + "data-structures", + "no-std", +] +license = "MIT OR Apache-2.0" +repository = "https://github.com/dtolnay/semver" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] +rustdoc-args = [ + "--cfg", + "doc_cfg", +] + +[lib] +doc-scrape-examples = false + +[dependencies.serde] +version = "1.0" +optional = true +default-features = false + +[features] +default = ["std"] +std = [] diff --git a/third_party/rust/semver/LICENSE-APACHE b/third_party/rust/semver/LICENSE-APACHE new file mode 100644 index 0000000000..16fe87b06e --- /dev/null +++ b/third_party/rust/semver/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/third_party/rust/semver/LICENSE-MIT b/third_party/rust/semver/LICENSE-MIT new file mode 100644 index 0000000000..31aa79387f --- /dev/null +++ b/third_party/rust/semver/LICENSE-MIT @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/third_party/rust/semver/README.md b/third_party/rust/semver/README.md new file mode 100644 index 0000000000..a9a1cb88b2 --- /dev/null +++ b/third_party/rust/semver/README.md @@ -0,0 +1,84 @@ +semver +====== + +[<img alt="github" src="https://img.shields.io/badge/github-dtolnay/semver-8da0cb?style=for-the-badge&labelColor=555555&logo=github" height="20">](https://github.com/dtolnay/semver) +[<img alt="crates.io" src="https://img.shields.io/crates/v/semver.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/semver) +[<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-semver-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs" height="20">](https://docs.rs/semver) +[<img alt="build status" src="https://img.shields.io/github/actions/workflow/status/dtolnay/semver/ci.yml?branch=master&style=for-the-badge" height="20">](https://github.com/dtolnay/semver/actions?query=branch%3Amaster) + +A parser and evaluator for Cargo's flavor of Semantic Versioning. + +Semantic Versioning (see <https://semver.org>) is a guideline for how version +numbers are assigned and incremented. It is widely followed within the +Cargo/crates.io ecosystem for Rust. + +```toml +[dependencies] +semver = "1.0" +``` + +*Compiler support: requires rustc 1.31+* + +<br> + +## Example + +```rust +use semver::{BuildMetadata, Prerelease, Version, VersionReq}; + +fn main() { + let req = VersionReq::parse(">=1.2.3, <1.8.0").unwrap(); + + // Check whether this requirement matches version 1.2.3-alpha.1 (no) + let version = Version { + major: 1, + minor: 2, + patch: 3, + pre: Prerelease::new("alpha.1").unwrap(), + build: BuildMetadata::EMPTY, + }; + assert!(!req.matches(&version)); + + // Check whether it matches 1.3.0 (yes it does) + let version = Version::parse("1.3.0").unwrap(); + assert!(req.matches(&version)); +} +``` + +<br> + +## Scope of this crate + +Besides Cargo, several other package ecosystems and package managers for other +languages also use SemVer: RubyGems/Bundler for Ruby, npm for JavaScript, +Composer for PHP, CocoaPods for Objective-C... + +The `semver` crate is specifically intended to implement Cargo's interpretation +of Semantic Versioning. + +Where the various tools differ in their interpretation or implementation of the +spec, this crate follows the implementation choices made by Cargo. If you are +operating on version numbers from some other package ecosystem, you will want to +use a different semver library which is appropriate to that ecosystem. + +The extent of Cargo's SemVer support is documented in the *[Specifying +Dependencies]* chapter of the Cargo reference. + +[Specifying Dependencies]: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html + +<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> diff --git a/third_party/rust/semver/benches/parse.rs b/third_party/rust/semver/benches/parse.rs new file mode 100644 index 0000000000..d6aded7802 --- /dev/null +++ b/third_party/rust/semver/benches/parse.rs @@ -0,0 +1,24 @@ +#![feature(test)] + +extern crate test; + +use semver::{Prerelease, Version, VersionReq}; +use test::{black_box, Bencher}; + +#[bench] +fn parse_prerelease(b: &mut Bencher) { + let text = "x.7.z.92"; + b.iter(|| black_box(text).parse::<Prerelease>().unwrap()); +} + +#[bench] +fn parse_version(b: &mut Bencher) { + let text = "1.0.2021-beta+exp.sha.5114f85"; + b.iter(|| black_box(text).parse::<Version>().unwrap()); +} + +#[bench] +fn parse_version_req(b: &mut Bencher) { + let text = ">=1.2.3, <2.0.0"; + b.iter(|| black_box(text).parse::<VersionReq>().unwrap()); +} diff --git a/third_party/rust/semver/build.rs b/third_party/rust/semver/build.rs new file mode 100644 index 0000000000..81ad970d8b --- /dev/null +++ b/third_party/rust/semver/build.rs @@ -0,0 +1,75 @@ +use std::env; +use std::process::Command; +use std::str; + +fn main() { + println!("cargo:rerun-if-changed=build.rs"); + + let compiler = match rustc_minor_version() { + Some(compiler) => compiler, + None => return, + }; + + if compiler < 33 { + // Exhaustive integer patterns. On older compilers, a final `_` arm is + // required even if every possible integer value is otherwise covered. + // https://github.com/rust-lang/rust/issues/50907 + println!("cargo:rustc-cfg=no_exhaustive_int_match"); + } + + if compiler < 36 { + // extern crate alloc. + // https://blog.rust-lang.org/2019/07/04/Rust-1.36.0.html#the-alloc-crate-is-stable + println!("cargo:rustc-cfg=no_alloc_crate"); + } + + if compiler < 39 { + // const Vec::new. + // https://doc.rust-lang.org/std/vec/struct.Vec.html#method.new + println!("cargo:rustc-cfg=no_const_vec_new"); + } + + if compiler < 40 { + // #[non_exhaustive]. + // https://blog.rust-lang.org/2019/12/19/Rust-1.40.0.html#non_exhaustive-structs-enums-and-variants + println!("cargo:rustc-cfg=no_non_exhaustive"); + } + + if compiler < 45 { + // String::strip_prefix. + // https://doc.rust-lang.org/std/primitive.str.html#method.strip_prefix + println!("cargo:rustc-cfg=no_str_strip_prefix"); + } + + if compiler < 46 { + // #[track_caller]. + // https://blog.rust-lang.org/2020/08/27/Rust-1.46.0.html#track_caller + println!("cargo:rustc-cfg=no_track_caller"); + } + + if compiler < 52 { + // #![deny(unsafe_op_in_unsafe_fn)]. + // https://github.com/rust-lang/rust/issues/71668 + println!("cargo:rustc-cfg=no_unsafe_op_in_unsafe_fn_lint"); + } + + if compiler < 53 { + // Efficient intrinsics for count-leading-zeros and count-trailing-zeros + // on NonZero integers stabilized in 1.53.0. On many architectures these + // are more efficient than counting zeros on ordinary zeroable integers. + // https://doc.rust-lang.org/std/num/struct.NonZeroU64.html#method.leading_zeros + // https://doc.rust-lang.org/std/num/struct.NonZeroU64.html#method.trailing_zeros + println!("cargo:rustc-cfg=no_nonzero_bitscan"); + } +} + +fn rustc_minor_version() -> Option<u32> { + let rustc = env::var_os("RUSTC")?; + let output = Command::new(rustc).arg("--version").output().ok()?; + let version = str::from_utf8(&output.stdout).ok()?; + let mut pieces = version.split('.'); + if pieces.next() != Some("rustc 1") { + return None; + } + pieces.next()?.parse().ok() +} diff --git a/third_party/rust/semver/src/backport.rs b/third_party/rust/semver/src/backport.rs new file mode 100644 index 0000000000..b5e1d02be5 --- /dev/null +++ b/third_party/rust/semver/src/backport.rs @@ -0,0 +1,23 @@ +#[cfg(no_str_strip_prefix)] // rustc <1.45 +pub(crate) trait StripPrefixExt { + fn strip_prefix(&self, ch: char) -> Option<&str>; +} + +#[cfg(no_str_strip_prefix)] +impl StripPrefixExt for str { + fn strip_prefix(&self, ch: char) -> Option<&str> { + if self.starts_with(ch) { + Some(&self[ch.len_utf8()..]) + } else { + None + } + } +} + +pub(crate) use crate::alloc::vec::Vec; + +#[cfg(no_alloc_crate)] // rustc <1.36 +pub(crate) mod alloc { + pub use std::alloc; + pub use std::vec; +} diff --git a/third_party/rust/semver/src/display.rs b/third_party/rust/semver/src/display.rs new file mode 100644 index 0000000000..3c2871bb97 --- /dev/null +++ b/third_party/rust/semver/src/display.rs @@ -0,0 +1,165 @@ +use crate::{BuildMetadata, Comparator, Op, Prerelease, Version, VersionReq}; +use core::fmt::{self, Alignment, Debug, Display, Write}; + +impl Display for Version { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + let do_display = |formatter: &mut fmt::Formatter| -> fmt::Result { + write!(formatter, "{}.{}.{}", self.major, self.minor, self.patch)?; + if !self.pre.is_empty() { + write!(formatter, "-{}", self.pre)?; + } + if !self.build.is_empty() { + write!(formatter, "+{}", self.build)?; + } + Ok(()) + }; + + let do_len = || -> usize { + digits(self.major) + + 1 + + digits(self.minor) + + 1 + + digits(self.patch) + + !self.pre.is_empty() as usize + + self.pre.len() + + !self.build.is_empty() as usize + + self.build.len() + }; + + pad(formatter, do_display, do_len) + } +} + +impl Display for VersionReq { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + if self.comparators.is_empty() { + return formatter.write_str("*"); + } + for (i, comparator) in self.comparators.iter().enumerate() { + if i > 0 { + formatter.write_str(", ")?; + } + write!(formatter, "{}", comparator)?; + } + Ok(()) + } +} + +impl Display for Comparator { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + let op = match self.op { + Op::Exact => "=", + Op::Greater => ">", + Op::GreaterEq => ">=", + Op::Less => "<", + Op::LessEq => "<=", + Op::Tilde => "~", + Op::Caret => "^", + Op::Wildcard => "", + #[cfg(no_non_exhaustive)] + Op::__NonExhaustive => unreachable!(), + }; + formatter.write_str(op)?; + write!(formatter, "{}", self.major)?; + if let Some(minor) = &self.minor { + write!(formatter, ".{}", minor)?; + if let Some(patch) = &self.patch { + write!(formatter, ".{}", patch)?; + if !self.pre.is_empty() { + write!(formatter, "-{}", self.pre)?; + } + } else if self.op == Op::Wildcard { + formatter.write_str(".*")?; + } + } else if self.op == Op::Wildcard { + formatter.write_str(".*")?; + } + Ok(()) + } +} + +impl Display for Prerelease { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str(self.as_str()) + } +} + +impl Display for BuildMetadata { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str(self.as_str()) + } +} + +impl Debug for Version { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + let mut debug = formatter.debug_struct("Version"); + debug + .field("major", &self.major) + .field("minor", &self.minor) + .field("patch", &self.patch); + if !self.pre.is_empty() { + debug.field("pre", &self.pre); + } + if !self.build.is_empty() { + debug.field("build", &self.build); + } + debug.finish() + } +} + +impl Debug for Prerelease { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "Prerelease(\"{}\")", self) + } +} + +impl Debug for BuildMetadata { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "BuildMetadata(\"{}\")", self) + } +} + +fn pad( + formatter: &mut fmt::Formatter, + do_display: impl FnOnce(&mut fmt::Formatter) -> fmt::Result, + do_len: impl FnOnce() -> usize, +) -> fmt::Result { + let min_width = match formatter.width() { + Some(min_width) => min_width, + None => return do_display(formatter), + }; + + let len = do_len(); + if len >= min_width { + return do_display(formatter); + } + + let default_align = Alignment::Left; + let align = formatter.align().unwrap_or(default_align); + let padding = min_width - len; + let (pre_pad, post_pad) = match align { + Alignment::Left => (0, padding), + Alignment::Right => (padding, 0), + Alignment::Center => (padding / 2, (padding + 1) / 2), + }; + + let fill = formatter.fill(); + for _ in 0..pre_pad { + formatter.write_char(fill)?; + } + + do_display(formatter)?; + + for _ in 0..post_pad { + formatter.write_char(fill)?; + } + Ok(()) +} + +fn digits(val: u64) -> usize { + if val < 10 { + 1 + } else { + 1 + digits(val / 10) + } +} diff --git a/third_party/rust/semver/src/error.rs b/third_party/rust/semver/src/error.rs new file mode 100644 index 0000000000..a514e3f11e --- /dev/null +++ b/third_party/rust/semver/src/error.rs @@ -0,0 +1,124 @@ +use crate::parse::Error; +use core::fmt::{self, Debug, Display}; + +pub(crate) enum ErrorKind { + UnexpectedEnd(Position), + UnexpectedChar(Position, char), + UnexpectedCharAfter(Position, char), + ExpectedCommaFound(Position, char), + LeadingZero(Position), + Overflow(Position), + EmptySegment(Position), + IllegalCharacter(Position), + WildcardNotTheOnlyComparator(char), + UnexpectedAfterWildcard, + ExcessiveComparators, +} + +#[derive(Copy, Clone, Eq, PartialEq)] +pub(crate) enum Position { + Major, + Minor, + Patch, + Pre, + Build, +} + +#[cfg(feature = "std")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] +impl std::error::Error for Error {} + +impl Display for Error { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + match &self.kind { + ErrorKind::UnexpectedEnd(pos) => { + write!(formatter, "unexpected end of input while parsing {}", pos) + } + ErrorKind::UnexpectedChar(pos, ch) => { + write!( + formatter, + "unexpected character {} while parsing {}", + QuotedChar(*ch), + pos, + ) + } + ErrorKind::UnexpectedCharAfter(pos, ch) => { + write!( + formatter, + "unexpected character {} after {}", + QuotedChar(*ch), + pos, + ) + } + ErrorKind::ExpectedCommaFound(pos, ch) => { + write!( + formatter, + "expected comma after {}, found {}", + pos, + QuotedChar(*ch), + ) + } + ErrorKind::LeadingZero(pos) => { + write!(formatter, "invalid leading zero in {}", pos) + } + ErrorKind::Overflow(pos) => { + write!(formatter, "value of {} exceeds u64::MAX", pos) + } + ErrorKind::EmptySegment(pos) => { + write!(formatter, "empty identifier segment in {}", pos) + } + ErrorKind::IllegalCharacter(pos) => { + write!(formatter, "unexpected character in {}", pos) + } + ErrorKind::WildcardNotTheOnlyComparator(ch) => { + write!( + formatter, + "wildcard req ({}) must be the only comparator in the version req", + ch, + ) + } + ErrorKind::UnexpectedAfterWildcard => { + formatter.write_str("unexpected character after wildcard in version req") + } + ErrorKind::ExcessiveComparators => { + formatter.write_str("excessive number of version comparators") + } + } + } +} + +impl Display for Position { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str(match self { + Position::Major => "major version number", + Position::Minor => "minor version number", + Position::Patch => "patch version number", + Position::Pre => "pre-release identifier", + Position::Build => "build metadata", + }) + } +} + +impl Debug for Error { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("Error(\"")?; + Display::fmt(self, formatter)?; + formatter.write_str("\")")?; + Ok(()) + } +} + +struct QuotedChar(char); + +impl Display for QuotedChar { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + // Standard library versions prior to https://github.com/rust-lang/rust/pull/95345 + // print character 0 as '\u{0}'. We prefer '\0' to keep error messages + // the same across all supported Rust versions. + if self.0 == '\0' { + formatter.write_str("'\\0'") + } else { + write!(formatter, "{:?}", self.0) + } + } +} diff --git a/third_party/rust/semver/src/eval.rs b/third_party/rust/semver/src/eval.rs new file mode 100644 index 0000000000..e6e38949a9 --- /dev/null +++ b/third_party/rust/semver/src/eval.rs @@ -0,0 +1,181 @@ +use crate::{Comparator, Op, Version, VersionReq}; + +pub(crate) fn matches_req(req: &VersionReq, ver: &Version) -> bool { + for cmp in &req.comparators { + if !matches_impl(cmp, ver) { + return false; + } + } + + if ver.pre.is_empty() { + return true; + } + + // If a version has a prerelease tag (for example, 1.2.3-alpha.3) then it + // will only be allowed to satisfy req if at least one comparator with the + // same major.minor.patch also has a prerelease tag. + for cmp in &req.comparators { + if pre_is_compatible(cmp, ver) { + return true; + } + } + + false +} + +pub(crate) fn matches_comparator(cmp: &Comparator, ver: &Version) -> bool { + matches_impl(cmp, ver) && (ver.pre.is_empty() || pre_is_compatible(cmp, ver)) +} + +fn matches_impl(cmp: &Comparator, ver: &Version) -> bool { + match cmp.op { + Op::Exact | Op::Wildcard => matches_exact(cmp, ver), + Op::Greater => matches_greater(cmp, ver), + Op::GreaterEq => matches_exact(cmp, ver) || matches_greater(cmp, ver), + Op::Less => matches_less(cmp, ver), + Op::LessEq => matches_exact(cmp, ver) || matches_less(cmp, ver), + Op::Tilde => matches_tilde(cmp, ver), + Op::Caret => matches_caret(cmp, ver), + #[cfg(no_non_exhaustive)] + Op::__NonExhaustive => unreachable!(), + } +} + +fn matches_exact(cmp: &Comparator, ver: &Version) -> bool { + if ver.major != cmp.major { + return false; + } + + if let Some(minor) = cmp.minor { + if ver.minor != minor { + return false; + } + } + + if let Some(patch) = cmp.patch { + if ver.patch != patch { + return false; + } + } + + ver.pre == cmp.pre +} + +fn matches_greater(cmp: &Comparator, ver: &Version) -> bool { + if ver.major != cmp.major { + return ver.major > cmp.major; + } + + match cmp.minor { + None => return false, + Some(minor) => { + if ver.minor != minor { + return ver.minor > minor; + } + } + } + + match cmp.patch { + None => return false, + Some(patch) => { + if ver.patch != patch { + return ver.patch > patch; + } + } + } + + ver.pre > cmp.pre +} + +fn matches_less(cmp: &Comparator, ver: &Version) -> bool { + if ver.major != cmp.major { + return ver.major < cmp.major; + } + + match cmp.minor { + None => return false, + Some(minor) => { + if ver.minor != minor { + return ver.minor < minor; + } + } + } + + match cmp.patch { + None => return false, + Some(patch) => { + if ver.patch != patch { + return ver.patch < patch; + } + } + } + + ver.pre < cmp.pre +} + +fn matches_tilde(cmp: &Comparator, ver: &Version) -> bool { + if ver.major != cmp.major { + return false; + } + + if let Some(minor) = cmp.minor { + if ver.minor != minor { + return false; + } + } + + if let Some(patch) = cmp.patch { + if ver.patch != patch { + return ver.patch > patch; + } + } + + ver.pre >= cmp.pre +} + +fn matches_caret(cmp: &Comparator, ver: &Version) -> bool { + if ver.major != cmp.major { + return false; + } + + let minor = match cmp.minor { + None => return true, + Some(minor) => minor, + }; + + let patch = match cmp.patch { + None => { + if cmp.major > 0 { + return ver.minor >= minor; + } else { + return ver.minor == minor; + } + } + Some(patch) => patch, + }; + + if cmp.major > 0 { + if ver.minor != minor { + return ver.minor > minor; + } else if ver.patch != patch { + return ver.patch > patch; + } + } else if minor > 0 { + if ver.minor != minor { + return false; + } else if ver.patch != patch { + return ver.patch > patch; + } + } else if ver.minor != minor || ver.patch != patch { + return false; + } + + ver.pre >= cmp.pre +} + +fn pre_is_compatible(cmp: &Comparator, ver: &Version) -> bool { + cmp.major == ver.major + && cmp.minor == Some(ver.minor) + && cmp.patch == Some(ver.patch) + && !cmp.pre.is_empty() +} diff --git a/third_party/rust/semver/src/identifier.rs b/third_party/rust/semver/src/identifier.rs new file mode 100644 index 0000000000..0273ae62a8 --- /dev/null +++ b/third_party/rust/semver/src/identifier.rs @@ -0,0 +1,422 @@ +// This module implements Identifier, a short-optimized string allowed to +// contain only the ASCII characters hyphen, dot, 0-9, A-Z, a-z. +// +// As of mid-2021, the distribution of pre-release lengths on crates.io is: +// +// length count length count length count +// 0 355929 11 81 24 2 +// 1 208 12 48 25 6 +// 2 236 13 55 26 10 +// 3 1909 14 25 27 4 +// 4 1284 15 15 28 1 +// 5 1742 16 35 30 1 +// 6 3440 17 9 31 5 +// 7 5624 18 6 32 1 +// 8 1321 19 12 36 2 +// 9 179 20 2 37 379 +// 10 65 23 11 +// +// and the distribution of build metadata lengths is: +// +// length count length count length count +// 0 364445 8 7725 18 1 +// 1 72 9 16 19 1 +// 2 7 10 85 20 1 +// 3 28 11 17 22 4 +// 4 9 12 10 26 1 +// 5 68 13 9 27 1 +// 6 73 14 10 40 5 +// 7 53 15 6 +// +// Therefore it really behooves us to be able to use the entire 8 bytes of a +// pointer for inline storage. For both pre-release and build metadata there are +// vastly more strings with length exactly 8 bytes than the sum over all lengths +// longer than 8 bytes. +// +// To differentiate the inline representation from the heap allocated long +// representation, we'll allocate heap pointers with 2-byte alignment so that +// they are guaranteed to have an unset least significant bit. Then in the repr +// we store for pointers, we rotate a 1 into the most significant bit of the +// most significant byte, which is never set for an ASCII byte. +// +// Inline repr: +// +// 0xxxxxxx 0xxxxxxx 0xxxxxxx 0xxxxxxx 0xxxxxxx 0xxxxxxx 0xxxxxxx 0xxxxxxx +// +// Heap allocated repr: +// +// 1ppppppp pppppppp pppppppp pppppppp pppppppp pppppppp pppppppp pppppppp 0 +// ^ most significant bit least significant bit of orig ptr, rotated out ^ +// +// Since the most significant bit doubles as a sign bit for the similarly sized +// signed integer type, the CPU has an efficient instruction for inspecting it, +// meaning we can differentiate between an inline repr and a heap allocated repr +// in one instruction. Effectively an inline repr always looks like a positive +// i64 while a heap allocated repr always looks like a negative i64. +// +// For the inline repr, we store \0 padding on the end of the stored characters, +// and thus the string length is readily determined efficiently by a cttz (count +// trailing zeros) or bsf (bit scan forward) instruction. +// +// For the heap allocated repr, the length is encoded as a base-128 varint at +// the head of the allocation. +// +// Empty strings are stored as an all-1 bit pattern, corresponding to -1i64. +// Consequently the all-0 bit pattern is never a legal representation in any +// repr, leaving it available as a niche for downstream code. For example this +// allows size_of::<Version>() == size_of::<Option<Version>>(). + +use crate::alloc::alloc::{alloc, dealloc, handle_alloc_error, Layout}; +use core::isize; +use core::mem; +use core::num::{NonZeroU64, NonZeroUsize}; +use core::ptr::{self, NonNull}; +use core::slice; +use core::str; +use core::usize; + +const PTR_BYTES: usize = mem::size_of::<NonNull<u8>>(); + +// If pointers are already 8 bytes or bigger, then 0. If pointers are smaller +// than 8 bytes, then Identifier will contain a byte array to raise its size up +// to 8 bytes total. +const TAIL_BYTES: usize = 8 * (PTR_BYTES < 8) as usize - PTR_BYTES * (PTR_BYTES < 8) as usize; + +#[repr(C, align(8))] +pub(crate) struct Identifier { + head: NonNull<u8>, + tail: [u8; TAIL_BYTES], +} + +impl Identifier { + pub(crate) const fn empty() -> Self { + // This is a separate constant because unsafe function calls are not + // allowed in a const fn body, only in a const, until later rustc than + // what we support. + const HEAD: NonNull<u8> = unsafe { NonNull::new_unchecked(!0 as *mut u8) }; + + // `mov rax, -1` + Identifier { + head: HEAD, + tail: [!0; TAIL_BYTES], + } + } + + // SAFETY: string must be ASCII and not contain \0 bytes. + pub(crate) unsafe fn new_unchecked(string: &str) -> Self { + let len = string.len(); + debug_assert!(len <= isize::MAX as usize); + match len as u64 { + 0 => Self::empty(), + 1..=8 => { + let mut bytes = [0u8; mem::size_of::<Identifier>()]; + // SAFETY: string is big enough to read len bytes, bytes is big + // enough to write len bytes, and they do not overlap. + unsafe { ptr::copy_nonoverlapping(string.as_ptr(), bytes.as_mut_ptr(), len) }; + // SAFETY: the head field is nonzero because the input string + // was at least 1 byte of ASCII and did not contain \0. + unsafe { mem::transmute::<[u8; mem::size_of::<Identifier>()], Identifier>(bytes) } + } + 9..=0xff_ffff_ffff_ffff => { + // SAFETY: len is in a range that does not contain 0. + let size = bytes_for_varint(unsafe { NonZeroUsize::new_unchecked(len) }) + len; + let align = 2; + // On 32-bit and 16-bit architecture, check for size overflowing + // isize::MAX. Making an allocation request bigger than this to + // the allocator is considered UB. All allocations (including + // static ones) are limited to isize::MAX so we're guaranteed + // len <= isize::MAX, and we know bytes_for_varint(len) <= 5 + // because 128**5 > isize::MAX, which means the only problem + // that can arise is when isize::MAX - 5 <= len <= isize::MAX. + // This is pretty much guaranteed to be malicious input so we + // don't need to care about returning a good error message. + if mem::size_of::<usize>() < 8 { + let max_alloc = usize::MAX / 2 - align; + assert!(size <= max_alloc); + } + // SAFETY: align is not zero, align is a power of two, and + // rounding size up to align does not overflow isize::MAX. + let layout = unsafe { Layout::from_size_align_unchecked(size, align) }; + // SAFETY: layout's size is nonzero. + let ptr = unsafe { alloc(layout) }; + if ptr.is_null() { + handle_alloc_error(layout); + } + let mut write = ptr; + let mut varint_remaining = len; + while varint_remaining > 0 { + // SAFETY: size is bytes_for_varint(len) bytes + len bytes. + // This is writing the first bytes_for_varint(len) bytes. + unsafe { ptr::write(write, varint_remaining as u8 | 0x80) }; + varint_remaining >>= 7; + // SAFETY: still in bounds of the same allocation. + write = unsafe { write.add(1) }; + } + // SAFETY: size is bytes_for_varint(len) bytes + len bytes. This + // is writing to the last len bytes. + unsafe { ptr::copy_nonoverlapping(string.as_ptr(), write, len) }; + Identifier { + head: ptr_to_repr(ptr), + tail: [0; TAIL_BYTES], + } + } + 0x100_0000_0000_0000..=0xffff_ffff_ffff_ffff => { + unreachable!("please refrain from storing >64 petabytes of text in semver version"); + } + #[cfg(no_exhaustive_int_match)] // rustc <1.33 + _ => unreachable!(), + } + } + + pub(crate) fn is_empty(&self) -> bool { + // `cmp rdi, -1` -- basically: `repr as i64 == -1` + let empty = Self::empty(); + let is_empty = self.head == empty.head && self.tail == empty.tail; + // The empty representation does nothing on Drop. We can't let this one + // drop normally because `impl Drop for Identifier` calls is_empty; that + // would be an infinite recursion. + mem::forget(empty); + is_empty + } + + fn is_inline(&self) -> bool { + // `test rdi, rdi` -- basically: `repr as i64 >= 0` + self.head.as_ptr() as usize >> (PTR_BYTES * 8 - 1) == 0 + } + + fn is_empty_or_inline(&self) -> bool { + // `cmp rdi, -2` -- basically: `repr as i64 > -2` + self.is_empty() || self.is_inline() + } + + pub(crate) fn as_str(&self) -> &str { + if self.is_empty() { + "" + } else if self.is_inline() { + // SAFETY: repr is in the inline representation. + unsafe { inline_as_str(self) } + } else { + // SAFETY: repr is in the heap allocated representation. + unsafe { ptr_as_str(&self.head) } + } + } +} + +impl Clone for Identifier { + fn clone(&self) -> Self { + if self.is_empty_or_inline() { + Identifier { + head: self.head, + tail: self.tail, + } + } else { + let ptr = repr_to_ptr(self.head); + // SAFETY: ptr is one of our own heap allocations. + let len = unsafe { decode_len(ptr) }; + let size = bytes_for_varint(len) + len.get(); + let align = 2; + // SAFETY: align is not zero, align is a power of two, and rounding + // size up to align does not overflow isize::MAX. This is just + // duplicating a previous allocation where all of these guarantees + // were already made. + let layout = unsafe { Layout::from_size_align_unchecked(size, align) }; + // SAFETY: layout's size is nonzero. + let clone = unsafe { alloc(layout) }; + if clone.is_null() { + handle_alloc_error(layout); + } + // SAFETY: new allocation cannot overlap the previous one (this was + // not a realloc). The argument ptrs are readable/writeable + // respectively for size bytes. + unsafe { ptr::copy_nonoverlapping(ptr, clone, size) } + Identifier { + head: ptr_to_repr(clone), + tail: [0; TAIL_BYTES], + } + } + } +} + +impl Drop for Identifier { + fn drop(&mut self) { + if self.is_empty_or_inline() { + return; + } + let ptr = repr_to_ptr_mut(self.head); + // SAFETY: ptr is one of our own heap allocations. + let len = unsafe { decode_len(ptr) }; + let size = bytes_for_varint(len) + len.get(); + let align = 2; + // SAFETY: align is not zero, align is a power of two, and rounding + // size up to align does not overflow usize::MAX. These guarantees were + // made when originally allocating this memory. + let layout = unsafe { Layout::from_size_align_unchecked(size, align) }; + // SAFETY: ptr was previously allocated by the same allocator with the + // same layout. + unsafe { dealloc(ptr, layout) } + } +} + +impl PartialEq for Identifier { + fn eq(&self, rhs: &Self) -> bool { + if self.is_empty_or_inline() { + // Fast path (most common) + self.head == rhs.head && self.tail == rhs.tail + } else if rhs.is_empty_or_inline() { + false + } else { + // SAFETY: both reprs are in the heap allocated representation. + unsafe { ptr_as_str(&self.head) == ptr_as_str(&rhs.head) } + } + } +} + +unsafe impl Send for Identifier {} +unsafe impl Sync for Identifier {} + +// We use heap pointers that are 2-byte aligned, meaning they have an +// insignificant 0 in the least significant bit. We take advantage of that +// unneeded bit to rotate a 1 into the most significant bit to make the repr +// distinguishable from ASCII bytes. +fn ptr_to_repr(original: *mut u8) -> NonNull<u8> { + // `mov eax, 1` + // `shld rax, rdi, 63` + let modified = (original as usize | 1).rotate_right(1); + + // `original + (modified - original)`, but being mindful of provenance. + let diff = modified.wrapping_sub(original as usize); + let modified = original.wrapping_add(diff); + + // SAFETY: the most significant bit of repr is known to be set, so the value + // is not zero. + unsafe { NonNull::new_unchecked(modified) } +} + +// Shift out the 1 previously placed into the most significant bit of the least +// significant byte. Shift in a low 0 bit to reconstruct the original 2-byte +// aligned pointer. +fn repr_to_ptr(modified: NonNull<u8>) -> *const u8 { + // `lea rax, [rdi + rdi]` + let modified = modified.as_ptr(); + let original = (modified as usize) << 1; + + // `modified + (original - modified)`, but being mindful of provenance. + let diff = original.wrapping_sub(modified as usize); + modified.wrapping_add(diff) +} + +fn repr_to_ptr_mut(repr: NonNull<u8>) -> *mut u8 { + repr_to_ptr(repr) as *mut u8 +} + +// Compute the length of the inline string, assuming the argument is in short +// string representation. Short strings are stored as 1 to 8 nonzero ASCII +// bytes, followed by \0 padding for the remaining bytes. +// +// SAFETY: the identifier must indeed be in the inline representation. +unsafe fn inline_len(repr: &Identifier) -> NonZeroUsize { + // SAFETY: Identifier's layout is align(8) and at least size 8. We're doing + // an aligned read of the first 8 bytes from it. The bytes are not all zero + // because inline strings are at least 1 byte long and cannot contain \0. + let repr = unsafe { ptr::read(repr as *const Identifier as *const NonZeroU64) }; + + // Rustc >=1.53 has intrinsics for counting zeros on a non-zeroable integer. + // On many architectures these are more efficient than counting on ordinary + // zeroable integers (bsf vs cttz). On rustc <1.53 without those intrinsics, + // we count zeros in the u64 rather than the NonZeroU64. + #[cfg(no_nonzero_bitscan)] + let repr = repr.get(); + + #[cfg(target_endian = "little")] + let zero_bits_on_string_end = repr.leading_zeros(); + #[cfg(target_endian = "big")] + let zero_bits_on_string_end = repr.trailing_zeros(); + + let nonzero_bytes = 8 - zero_bits_on_string_end as usize / 8; + + // SAFETY: repr is nonzero, so it has at most 63 zero bits on either end, + // thus at least one nonzero byte. + unsafe { NonZeroUsize::new_unchecked(nonzero_bytes) } +} + +// SAFETY: repr must be in the inline representation, i.e. at least 1 and at +// most 8 nonzero ASCII bytes padded on the end with \0 bytes. +unsafe fn inline_as_str(repr: &Identifier) -> &str { + let ptr = repr as *const Identifier as *const u8; + let len = unsafe { inline_len(repr) }.get(); + // SAFETY: we are viewing the nonzero ASCII prefix of the inline repr's + // contents as a slice of bytes. Input/output lifetimes are correctly + // associated. + let slice = unsafe { slice::from_raw_parts(ptr, len) }; + // SAFETY: the string contents are known to be only ASCII bytes, which are + // always valid UTF-8. + unsafe { str::from_utf8_unchecked(slice) } +} + +// Decode varint. Varints consist of between one and eight base-128 digits, each +// of which is stored in a byte with most significant bit set. Adjacent to the +// varint in memory there is guaranteed to be at least 9 ASCII bytes, each of +// which has an unset most significant bit. +// +// SAFETY: ptr must be one of our own heap allocations, with the varint header +// already written. +unsafe fn decode_len(ptr: *const u8) -> NonZeroUsize { + // SAFETY: There is at least one byte of varint followed by at least 9 bytes + // of string content, which is at least 10 bytes total for the allocation, + // so reading the first two is no problem. + let [first, second] = unsafe { ptr::read(ptr as *const [u8; 2]) }; + if second < 0x80 { + // SAFETY: the length of this heap allocated string has been encoded as + // one base-128 digit, so the length is at least 9 and at most 127. It + // cannot be zero. + unsafe { NonZeroUsize::new_unchecked((first & 0x7f) as usize) } + } else { + return unsafe { decode_len_cold(ptr) }; + + // Identifiers 128 bytes or longer. This is not exercised by any crate + // version currently published to crates.io. + #[cold] + #[inline(never)] + unsafe fn decode_len_cold(mut ptr: *const u8) -> NonZeroUsize { + let mut len = 0; + let mut shift = 0; + loop { + // SAFETY: varint continues while there are bytes having the + // most significant bit set, i.e. until we start hitting the + // ASCII string content with msb unset. + let byte = unsafe { *ptr }; + if byte < 0x80 { + // SAFETY: the string length is known to be 128 bytes or + // longer. + return unsafe { NonZeroUsize::new_unchecked(len) }; + } + // SAFETY: still in bounds of the same allocation. + ptr = unsafe { ptr.add(1) }; + len += ((byte & 0x7f) as usize) << shift; + shift += 7; + } + } + } +} + +// SAFETY: repr must be in the heap allocated representation, with varint header +// and string contents already written. +unsafe fn ptr_as_str(repr: &NonNull<u8>) -> &str { + let ptr = repr_to_ptr(*repr); + let len = unsafe { decode_len(ptr) }; + let header = bytes_for_varint(len); + let slice = unsafe { slice::from_raw_parts(ptr.add(header), len.get()) }; + // SAFETY: all identifier contents are ASCII bytes, which are always valid + // UTF-8. + unsafe { str::from_utf8_unchecked(slice) } +} + +// Number of base-128 digits required for the varint representation of a length. +fn bytes_for_varint(len: NonZeroUsize) -> usize { + #[cfg(no_nonzero_bitscan)] // rustc <1.53 + let len = len.get(); + + let usize_bits = mem::size_of::<usize>() * 8; + let len_bits = usize_bits - len.leading_zeros() as usize; + (len_bits + 6) / 7 +} diff --git a/third_party/rust/semver/src/impls.rs b/third_party/rust/semver/src/impls.rs new file mode 100644 index 0000000000..c3b6c60133 --- /dev/null +++ b/third_party/rust/semver/src/impls.rs @@ -0,0 +1,156 @@ +use crate::backport::*; +use crate::identifier::Identifier; +use crate::{BuildMetadata, Comparator, Prerelease, VersionReq}; +use core::cmp::Ordering; +use core::hash::{Hash, Hasher}; +use core::iter::FromIterator; +use core::ops::Deref; + +impl Default for Identifier { + fn default() -> Self { + Identifier::empty() + } +} + +impl Eq for Identifier {} + +impl Hash for Identifier { + fn hash<H: Hasher>(&self, hasher: &mut H) { + self.as_str().hash(hasher); + } +} + +impl Deref for Prerelease { + type Target = str; + + fn deref(&self) -> &Self::Target { + self.identifier.as_str() + } +} + +impl Deref for BuildMetadata { + type Target = str; + + fn deref(&self) -> &Self::Target { + self.identifier.as_str() + } +} + +impl PartialOrd for Prerelease { + fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> { + Some(Ord::cmp(self, rhs)) + } +} + +impl PartialOrd for BuildMetadata { + fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> { + Some(Ord::cmp(self, rhs)) + } +} + +impl Ord for Prerelease { + fn cmp(&self, rhs: &Self) -> Ordering { + match self.is_empty() { + true if rhs.is_empty() => return Ordering::Equal, + // A real release compares greater than prerelease. + true => return Ordering::Greater, + // Prerelease compares less than the real release. + false if rhs.is_empty() => return Ordering::Less, + false => {} + } + + let lhs = self.as_str().split('.'); + let mut rhs = rhs.as_str().split('.'); + + for lhs in lhs { + let rhs = match rhs.next() { + // Spec: "A larger set of pre-release fields has a higher + // precedence than a smaller set, if all of the preceding + // identifiers are equal." + None => return Ordering::Greater, + Some(rhs) => rhs, + }; + + let string_cmp = || Ord::cmp(lhs, rhs); + let is_ascii_digit = |b: u8| b.is_ascii_digit(); + let ordering = match ( + lhs.bytes().all(is_ascii_digit), + rhs.bytes().all(is_ascii_digit), + ) { + // Respect numeric ordering, for example 99 < 100. Spec says: + // "Identifiers consisting of only digits are compared + // numerically." + (true, true) => Ord::cmp(&lhs.len(), &rhs.len()).then_with(string_cmp), + // Spec: "Numeric identifiers always have lower precedence than + // non-numeric identifiers." + (true, false) => return Ordering::Less, + (false, true) => return Ordering::Greater, + // Spec: "Identifiers with letters or hyphens are compared + // lexically in ASCII sort order." + (false, false) => string_cmp(), + }; + + if ordering != Ordering::Equal { + return ordering; + } + } + + if rhs.next().is_none() { + Ordering::Equal + } else { + Ordering::Less + } + } +} + +impl Ord for BuildMetadata { + fn cmp(&self, rhs: &Self) -> Ordering { + let lhs = self.as_str().split('.'); + let mut rhs = rhs.as_str().split('.'); + + for lhs in lhs { + let rhs = match rhs.next() { + None => return Ordering::Greater, + Some(rhs) => rhs, + }; + + let is_ascii_digit = |b: u8| b.is_ascii_digit(); + let ordering = match ( + lhs.bytes().all(is_ascii_digit), + rhs.bytes().all(is_ascii_digit), + ) { + (true, true) => { + // 0 < 00 < 1 < 01 < 001 < 2 < 02 < 002 < 10 + let lhval = lhs.trim_start_matches('0'); + let rhval = rhs.trim_start_matches('0'); + Ord::cmp(&lhval.len(), &rhval.len()) + .then_with(|| Ord::cmp(lhval, rhval)) + .then_with(|| Ord::cmp(&lhs.len(), &rhs.len())) + } + (true, false) => return Ordering::Less, + (false, true) => return Ordering::Greater, + (false, false) => Ord::cmp(lhs, rhs), + }; + + if ordering != Ordering::Equal { + return ordering; + } + } + + if rhs.next().is_none() { + Ordering::Equal + } else { + Ordering::Less + } + } +} + +impl FromIterator<Comparator> for VersionReq { + fn from_iter<I>(iter: I) -> Self + where + I: IntoIterator<Item = Comparator>, + { + let comparators = Vec::from_iter(iter); + VersionReq { comparators } + } +} diff --git a/third_party/rust/semver/src/lib.rs b/third_party/rust/semver/src/lib.rs new file mode 100644 index 0000000000..32ed96d1ca --- /dev/null +++ b/third_party/rust/semver/src/lib.rs @@ -0,0 +1,533 @@ +//! [![github]](https://github.com/dtolnay/semver) [![crates-io]](https://crates.io/crates/semver) [![docs-rs]](https://docs.rs/semver) +//! +//! [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&logo=docs.rs +//! +//! <br> +//! +//! A parser and evaluator for Cargo's flavor of Semantic Versioning. +//! +//! Semantic Versioning (see <https://semver.org>) is a guideline for how +//! version numbers are assigned and incremented. It is widely followed within +//! the Cargo/crates.io ecosystem for Rust. +//! +//! <br> +//! +//! # Example +//! +//! ``` +//! use semver::{BuildMetadata, Prerelease, Version, VersionReq}; +//! +//! fn main() { +//! let req = VersionReq::parse(">=1.2.3, <1.8.0").unwrap(); +//! +//! // Check whether this requirement matches version 1.2.3-alpha.1 (no) +//! let version = Version { +//! major: 1, +//! minor: 2, +//! patch: 3, +//! pre: Prerelease::new("alpha.1").unwrap(), +//! build: BuildMetadata::EMPTY, +//! }; +//! assert!(!req.matches(&version)); +//! +//! // Check whether it matches 1.3.0 (yes it does) +//! let version = Version::parse("1.3.0").unwrap(); +//! assert!(req.matches(&version)); +//! } +//! ``` +//! +//! <br><br> +//! +//! # Scope of this crate +//! +//! Besides Cargo, several other package ecosystems and package managers for +//! other languages also use SemVer: RubyGems/Bundler for Ruby, npm for +//! JavaScript, Composer for PHP, CocoaPods for Objective-C... +//! +//! The `semver` crate is specifically intended to implement Cargo's +//! interpretation of Semantic Versioning. +//! +//! Where the various tools differ in their interpretation or implementation of +//! the spec, this crate follows the implementation choices made by Cargo. If +//! you are operating on version numbers from some other package ecosystem, you +//! will want to use a different semver library which is appropriate to that +//! ecosystem. +//! +//! The extent of Cargo's SemVer support is documented in the *[Specifying +//! Dependencies]* chapter of the Cargo reference. +//! +//! [Specifying Dependencies]: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html + +#![doc(html_root_url = "https://docs.rs/semver/1.0.16")] +#![cfg_attr(doc_cfg, feature(doc_cfg))] +#![cfg_attr(all(not(feature = "std"), not(no_alloc_crate)), no_std)] +#![cfg_attr(not(no_unsafe_op_in_unsafe_fn_lint), deny(unsafe_op_in_unsafe_fn))] +#![cfg_attr(no_unsafe_op_in_unsafe_fn_lint, allow(unused_unsafe))] +#![cfg_attr(no_str_strip_prefix, allow(unstable_name_collisions))] +#![allow( + clippy::cast_lossless, + clippy::cast_possible_truncation, + clippy::doc_markdown, + clippy::items_after_statements, + clippy::manual_map, + clippy::match_bool, + clippy::missing_errors_doc, + clippy::must_use_candidate, + clippy::needless_doctest_main, + clippy::option_if_let_else, + clippy::ptr_as_ptr, + clippy::redundant_else, + clippy::semicolon_if_nothing_returned, // https://github.com/rust-lang/rust-clippy/issues/7324 + clippy::similar_names, + clippy::unnested_or_patterns, + clippy::unseparated_literal_suffix, + clippy::wildcard_imports +)] + +#[cfg(not(no_alloc_crate))] +extern crate alloc; + +mod backport; +mod display; +mod error; +mod eval; +mod identifier; +mod impls; +mod parse; + +#[cfg(feature = "serde")] +mod serde; + +use crate::alloc::vec::Vec; +use crate::identifier::Identifier; +use core::str::FromStr; + +#[allow(unused_imports)] +use crate::backport::*; + +pub use crate::parse::Error; + +/// **SemVer version** as defined by <https://semver.org>. +/// +/// # Syntax +/// +/// - The major, minor, and patch numbers may be any integer 0 through u64::MAX. +/// When representing a SemVer version as a string, each number is written as +/// a base 10 integer. For example, `1.0.119`. +/// +/// - Leading zeros are forbidden in those positions. For example `1.01.00` is +/// invalid as a SemVer version. +/// +/// - The pre-release identifier, if present, must conform to the syntax +/// documented for [`Prerelease`]. +/// +/// - The build metadata, if present, must conform to the syntax documented for +/// [`BuildMetadata`]. +/// +/// - Whitespace is not allowed anywhere in the version. +/// +/// # Total ordering +/// +/// Given any two SemVer versions, one is less than, greater than, or equal to +/// the other. Versions may be compared against one another using Rust's usual +/// comparison operators. +/// +/// - The major, minor, and patch number are compared numerically from left to +/// right, lexicographically ordered as a 3-tuple of integers. So for example +/// version `1.5.0` is less than version `1.19.0`, despite the fact that +/// "1.19.0" < "1.5.0" as ASCIIbetically compared strings and 1.19 < 1.5 +/// as real numbers. +/// +/// - When major, minor, and patch are equal, a pre-release version is +/// considered less than the ordinary release: version `1.0.0-alpha.1` is +/// less than version `1.0.0`. +/// +/// - Two pre-releases of the same major, minor, patch are compared by +/// lexicographic ordering of dot-separated components of the pre-release +/// string. +/// +/// - Identifiers consisting of only digits are compared +/// numerically: `1.0.0-pre.8` is less than `1.0.0-pre.12`. +/// +/// - Identifiers that contain a letter or hyphen are compared in ASCII sort +/// order: `1.0.0-pre12` is less than `1.0.0-pre8`. +/// +/// - Any numeric identifier is always less than any non-numeric +/// identifier: `1.0.0-pre.1` is less than `1.0.0-pre.x`. +/// +/// Example: `1.0.0-alpha` < `1.0.0-alpha.1` < `1.0.0-alpha.beta` < `1.0.0-beta` < `1.0.0-beta.2` < `1.0.0-beta.11` < `1.0.0-rc.1` < `1.0.0` +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct Version { + pub major: u64, + pub minor: u64, + pub patch: u64, + pub pre: Prerelease, + pub build: BuildMetadata, +} + +/// **SemVer version requirement** describing the intersection of some version +/// comparators, such as `>=1.2.3, <1.8`. +/// +/// # Syntax +/// +/// - Either `*` (meaning "any"), or one or more comma-separated comparators. +/// +/// - A [`Comparator`] is an operator ([`Op`]) and a partial version, separated +/// by optional whitespace. For example `>=1.0.0` or `>=1.0`. +/// +/// - Build metadata is syntactically permitted on the partial versions, but is +/// completely ignored, as it's never relevant to whether any comparator +/// matches a particular version. +/// +/// - Whitespace is permitted around commas and around operators. Whitespace is +/// not permitted within a partial version, i.e. anywhere between the major +/// version number and its minor, patch, pre-release, or build metadata. +#[derive(Clone, Eq, PartialEq, Hash, Debug)] +#[cfg_attr(no_const_vec_new, derive(Default))] +pub struct VersionReq { + pub comparators: Vec<Comparator>, +} + +/// A pair of comparison operator and partial version, such as `>=1.2`. Forms +/// one piece of a VersionReq. +#[derive(Clone, Eq, PartialEq, Hash, Debug)] +pub struct Comparator { + pub op: Op, + pub major: u64, + pub minor: Option<u64>, + /// Patch is only allowed if minor is Some. + pub patch: Option<u64>, + /// Non-empty pre-release is only allowed if patch is Some. + pub pre: Prerelease, +} + +/// SemVer comparison operator: `=`, `>`, `>=`, `<`, `<=`, `~`, `^`, `*`. +/// +/// # Op::Exact +/// -  **`=I.J.K`** — exactly the version I.J.K +/// -  **`=I.J`** — equivalent to `>=I.J.0, <I.(J+1).0` +/// -  **`=I`** — equivalent to `>=I.0.0, <(I+1).0.0` +/// +/// # Op::Greater +/// -  **`>I.J.K`** +/// -  **`>I.J`** — equivalent to `>=I.(J+1).0` +/// -  **`>I`** — equivalent to `>=(I+1).0.0` +/// +/// # Op::GreaterEq +/// -  **`>=I.J.K`** +/// -  **`>=I.J`** — equivalent to `>=I.J.0` +/// -  **`>=I`** — equivalent to `>=I.0.0` +/// +/// # Op::Less +/// -  **`<I.J.K`** +/// -  **`<I.J`** — equivalent to `<I.J.0` +/// -  **`<I`** — equivalent to `<I.0.0` +/// +/// # Op::LessEq +/// -  **`<=I.J.K`** +/// -  **`<=I.J`** — equivalent to `<I.(J+1).0` +/// -  **`<=I`** — equivalent to `<(I+1).0.0` +/// +/// # Op::Tilde ("patch" updates) +/// *Tilde requirements allow the **patch** part of the semver version (the third number) to increase.* +/// -  **`~I.J.K`** — equivalent to `>=I.J.K, <I.(J+1).0` +/// -  **`~I.J`** — equivalent to `=I.J` +/// -  **`~I`** — equivalent to `=I` +/// +/// # Op::Caret ("compatible" updates) +/// *Caret requirements allow parts that are **right of the first nonzero** part of the semver version to increase.* +/// -  **`^I.J.K`** (for I\>0) — equivalent to `>=I.J.K, <(I+1).0.0` +/// -  **`^0.J.K`** (for J\>0) — equivalent to `>=0.J.K, <0.(J+1).0` +/// -  **`^0.0.K`** — equivalent to `=0.0.K` +/// -  **`^I.J`** (for I\>0 or J\>0) — equivalent to `^I.J.0` +/// -  **`^0.0`** — equivalent to `=0.0` +/// -  **`^I`** — equivalent to `=I` +/// +/// # Op::Wildcard +/// -  **`I.J.*`** — equivalent to `=I.J` +/// -  **`I.*`** or **`I.*.*`** — equivalent to `=I` +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +#[cfg_attr(not(no_non_exhaustive), non_exhaustive)] +pub enum Op { + Exact, + Greater, + GreaterEq, + Less, + LessEq, + Tilde, + Caret, + Wildcard, + + #[cfg(no_non_exhaustive)] // rustc <1.40 + #[doc(hidden)] + __NonExhaustive, +} + +/// Optional pre-release identifier on a version string. This comes after `-` in +/// a SemVer version, like `1.0.0-alpha.1` +/// +/// # Examples +/// +/// Some real world pre-release idioms drawn from crates.io: +/// +/// - **[mio]** <code>0.7.0-<b>alpha.1</b></code> — the most common style +/// for numbering pre-releases. +/// +/// - **[pest]** <code>1.0.0-<b>beta.8</b></code>, <code>1.0.0-<b>rc.0</b></code> +/// — this crate makes a distinction between betas and release +/// candidates. +/// +/// - **[sassers]** <code>0.11.0-<b>shitshow</b></code> — ???. +/// +/// - **[atomic-utils]** <code>0.0.0-<b>reserved</b></code> — a squatted +/// crate name. +/// +/// [mio]: https://crates.io/crates/mio +/// [pest]: https://crates.io/crates/pest +/// [atomic-utils]: https://crates.io/crates/atomic-utils +/// [sassers]: https://crates.io/crates/sassers +/// +/// *Tip:* Be aware that if you are planning to number your own pre-releases, +/// you should prefer to separate the numeric part from any non-numeric +/// identifiers by using a dot in between. That is, prefer pre-releases +/// `alpha.1`, `alpha.2`, etc rather than `alpha1`, `alpha2` etc. The SemVer +/// spec's rule for pre-release precedence has special treatment of numeric +/// components in the pre-release string, but only if there are no non-digit +/// characters in the same dot-separated component. So you'd have `alpha.2` < +/// `alpha.11` as intended, but `alpha11` < `alpha2`. +/// +/// # Syntax +/// +/// Pre-release strings are a series of dot separated identifiers immediately +/// following the patch version. Identifiers must comprise only ASCII +/// alphanumerics and hyphens: `0-9`, `A-Z`, `a-z`, `-`. Identifiers must not be +/// empty. Numeric identifiers must not include leading zeros. +/// +/// # Total ordering +/// +/// Pre-releases have a total order defined by the SemVer spec. It uses +/// lexicographic ordering of dot-separated components. Identifiers consisting +/// of only digits are compared numerically. Otherwise, identifiers are compared +/// in ASCII sort order. Any numeric identifier is always less than any +/// non-numeric identifier. +/// +/// Example: `alpha` < `alpha.85` < `alpha.90` < `alpha.200` < `alpha.0a` < `alpha.1a0` < `alpha.a` < `beta` +#[derive(Default, Clone, Eq, PartialEq, Hash)] +pub struct Prerelease { + identifier: Identifier, +} + +/// Optional build metadata identifier. This comes after `+` in a SemVer +/// version, as in `0.8.1+zstd.1.5.0`. +/// +/// # Examples +/// +/// Some real world build metadata idioms drawn from crates.io: +/// +/// - **[libgit2-sys]** <code>0.12.20+<b>1.1.0</b></code> — for this +/// crate, the build metadata indicates the version of the C libgit2 library +/// that the Rust crate is built against. +/// +/// - **[mashup]** <code>0.1.13+<b>deprecated</b></code> — just the word +/// "deprecated" for a crate that has been superseded by another. Eventually +/// people will take notice of this in Cargo's build output where it lists the +/// crates being compiled. +/// +/// - **[google-bigquery2]** <code>2.0.4+<b>20210327</b></code> — this +/// library is automatically generated from an official API schema, and the +/// build metadata indicates the date on which that schema was last captured. +/// +/// - **[fbthrift-git]** <code>0.0.6+<b>c7fcc0e</b></code> — this crate is +/// published from snapshots of a big company monorepo. In monorepo +/// development, there is no concept of versions, and all downstream code is +/// just updated atomically in the same commit that breaking changes to a +/// library are landed. Therefore for crates.io purposes, every published +/// version must be assumed to be incompatible with the previous. The build +/// metadata provides the source control hash of the snapshotted code. +/// +/// [libgit2-sys]: https://crates.io/crates/libgit2-sys +/// [mashup]: https://crates.io/crates/mashup +/// [google-bigquery2]: https://crates.io/crates/google-bigquery2 +/// [fbthrift-git]: https://crates.io/crates/fbthrift-git +/// +/// # Syntax +/// +/// Build metadata is a series of dot separated identifiers immediately +/// following the patch or pre-release version. Identifiers must comprise only +/// ASCII alphanumerics and hyphens: `0-9`, `A-Z`, `a-z`, `-`. Identifiers must +/// not be empty. Leading zeros *are* allowed, unlike any other place in the +/// SemVer grammar. +/// +/// # Total ordering +/// +/// Build metadata is ignored in evaluating `VersionReq`; it plays no role in +/// whether a `Version` matches any one of the comparison operators. +/// +/// However for comparing build metadatas among one another, they do have a +/// total order which is determined by lexicographic ordering of dot-separated +/// components. Identifiers consisting of only digits are compared numerically. +/// Otherwise, identifiers are compared in ASCII sort order. Any numeric +/// identifier is always less than any non-numeric identifier. +/// +/// Example: `demo` < `demo.85` < `demo.90` < `demo.090` < `demo.200` < `demo.1a0` < `demo.a` < `memo` +#[derive(Default, Clone, Eq, PartialEq, Hash)] +pub struct BuildMetadata { + identifier: Identifier, +} + +impl Version { + /// Create `Version` with an empty pre-release and build metadata. + /// + /// Equivalent to: + /// + /// ``` + /// # use semver::{BuildMetadata, Prerelease, Version}; + /// # + /// # const fn new(major: u64, minor: u64, patch: u64) -> Version { + /// Version { + /// major, + /// minor, + /// patch, + /// pre: Prerelease::EMPTY, + /// build: BuildMetadata::EMPTY, + /// } + /// # } + /// ``` + pub const fn new(major: u64, minor: u64, patch: u64) -> Self { + Version { + major, + minor, + patch, + pre: Prerelease::EMPTY, + build: BuildMetadata::EMPTY, + } + } + + /// Create `Version` by parsing from string representation. + /// + /// # Errors + /// + /// Possible reasons for the parse to fail include: + /// + /// - `1.0` — too few numeric components. A SemVer version must have + /// exactly three. If you are looking at something that has fewer than + /// three numbers in it, it's possible it is a `VersionReq` instead (with + /// an implicit default `^` comparison operator). + /// + /// - `1.0.01` — a numeric component has a leading zero. + /// + /// - `1.0.unknown` — unexpected character in one of the components. + /// + /// - `1.0.0-` or `1.0.0+` — the pre-release or build metadata are + /// indicated present but empty. + /// + /// - `1.0.0-alpha_123` — pre-release or build metadata have something + /// outside the allowed characters, which are `0-9`, `A-Z`, `a-z`, `-`, + /// and `.` (dot). + /// + /// - `23456789999999999999.0.0` — overflow of a u64. + pub fn parse(text: &str) -> Result<Self, Error> { + Version::from_str(text) + } +} + +impl VersionReq { + /// A `VersionReq` with no constraint on the version numbers it matches. + /// Equivalent to `VersionReq::parse("*").unwrap()`. + /// + /// In terms of comparators this is equivalent to `>=0.0.0`. + /// + /// Counterintuitively a `*` VersionReq does not match every possible + /// version number. In particular, in order for *any* `VersionReq` to match + /// a pre-release version, the `VersionReq` must contain at least one + /// `Comparator` that has an explicit major, minor, and patch version + /// identical to the pre-release being matched, and that has a nonempty + /// pre-release component. Since `*` is not written with an explicit major, + /// minor, and patch version, and does not contain a nonempty pre-release + /// component, it does not match any pre-release versions. + #[cfg(not(no_const_vec_new))] // rustc <1.39 + pub const STAR: Self = VersionReq { + comparators: Vec::new(), + }; + + /// Create `VersionReq` by parsing from string representation. + /// + /// # Errors + /// + /// Possible reasons for the parse to fail include: + /// + /// - `>a.b` — unexpected characters in the partial version. + /// + /// - `@1.0.0` — unrecognized comparison operator. + /// + /// - `^1.0.0, ` — unexpected end of input. + /// + /// - `>=1.0 <2.0` — missing comma between comparators. + /// + /// - `*.*` — unsupported wildcard syntax. + pub fn parse(text: &str) -> Result<Self, Error> { + VersionReq::from_str(text) + } + + /// Evaluate whether the given `Version` satisfies the version requirement + /// described by `self`. + pub fn matches(&self, version: &Version) -> bool { + eval::matches_req(self, version) + } +} + +/// The default VersionReq is the same as [`VersionReq::STAR`]. +#[cfg(not(no_const_vec_new))] +impl Default for VersionReq { + fn default() -> Self { + VersionReq::STAR + } +} + +impl Comparator { + pub fn parse(text: &str) -> Result<Self, Error> { + Comparator::from_str(text) + } + + pub fn matches(&self, version: &Version) -> bool { + eval::matches_comparator(self, version) + } +} + +impl Prerelease { + pub const EMPTY: Self = Prerelease { + identifier: Identifier::empty(), + }; + + pub fn new(text: &str) -> Result<Self, Error> { + Prerelease::from_str(text) + } + + pub fn as_str(&self) -> &str { + self.identifier.as_str() + } + + pub fn is_empty(&self) -> bool { + self.identifier.is_empty() + } +} + +impl BuildMetadata { + pub const EMPTY: Self = BuildMetadata { + identifier: Identifier::empty(), + }; + + pub fn new(text: &str) -> Result<Self, Error> { + BuildMetadata::from_str(text) + } + + pub fn as_str(&self) -> &str { + self.identifier.as_str() + } + + pub fn is_empty(&self) -> bool { + self.identifier.is_empty() + } +} diff --git a/third_party/rust/semver/src/parse.rs b/third_party/rust/semver/src/parse.rs new file mode 100644 index 0000000000..6a8f6ffba4 --- /dev/null +++ b/third_party/rust/semver/src/parse.rs @@ -0,0 +1,405 @@ +use crate::backport::*; +use crate::error::{ErrorKind, Position}; +use crate::identifier::Identifier; +use crate::{BuildMetadata, Comparator, Op, Prerelease, Version, VersionReq}; +use core::str::FromStr; + +/// Error parsing a SemVer version or version requirement. +/// +/// # Example +/// +/// ``` +/// use semver::Version; +/// +/// fn main() { +/// let err = Version::parse("1.q.r").unwrap_err(); +/// +/// // "unexpected character 'q' while parsing minor version number" +/// eprintln!("{}", err); +/// } +/// ``` +pub struct Error { + pub(crate) kind: ErrorKind, +} + +impl FromStr for Version { + type Err = Error; + + fn from_str(text: &str) -> Result<Self, Self::Err> { + let mut pos = Position::Major; + let (major, text) = numeric_identifier(text, pos)?; + let text = dot(text, pos)?; + + pos = Position::Minor; + let (minor, text) = numeric_identifier(text, pos)?; + let text = dot(text, pos)?; + + pos = Position::Patch; + let (patch, text) = numeric_identifier(text, pos)?; + + if text.is_empty() { + return Ok(Version::new(major, minor, patch)); + } + + let (pre, text) = if let Some(text) = text.strip_prefix('-') { + pos = Position::Pre; + let (pre, text) = prerelease_identifier(text)?; + if pre.is_empty() { + return Err(Error::new(ErrorKind::EmptySegment(pos))); + } + (pre, text) + } else { + (Prerelease::EMPTY, text) + }; + + let (build, text) = if let Some(text) = text.strip_prefix('+') { + pos = Position::Build; + let (build, text) = build_identifier(text)?; + if build.is_empty() { + return Err(Error::new(ErrorKind::EmptySegment(pos))); + } + (build, text) + } else { + (BuildMetadata::EMPTY, text) + }; + + if let Some(unexpected) = text.chars().next() { + return Err(Error::new(ErrorKind::UnexpectedCharAfter(pos, unexpected))); + } + + Ok(Version { + major, + minor, + patch, + pre, + build, + }) + } +} + +impl FromStr for VersionReq { + type Err = Error; + + fn from_str(text: &str) -> Result<Self, Self::Err> { + let text = text.trim_start_matches(' '); + if let Some((ch, text)) = wildcard(text) { + let rest = text.trim_start_matches(' '); + if rest.is_empty() { + #[cfg(not(no_const_vec_new))] + return Ok(VersionReq::STAR); + #[cfg(no_const_vec_new)] // rustc <1.39 + return Ok(VersionReq { + comparators: Vec::new(), + }); + } else if rest.starts_with(',') { + return Err(Error::new(ErrorKind::WildcardNotTheOnlyComparator(ch))); + } else { + return Err(Error::new(ErrorKind::UnexpectedAfterWildcard)); + } + } + + let depth = 0; + let mut comparators = Vec::new(); + let len = version_req(text, &mut comparators, depth)?; + unsafe { comparators.set_len(len) } + Ok(VersionReq { comparators }) + } +} + +impl FromStr for Comparator { + type Err = Error; + + fn from_str(text: &str) -> Result<Self, Self::Err> { + let text = text.trim_start_matches(' '); + let (comparator, pos, rest) = comparator(text)?; + if !rest.is_empty() { + let unexpected = rest.chars().next().unwrap(); + return Err(Error::new(ErrorKind::UnexpectedCharAfter(pos, unexpected))); + } + Ok(comparator) + } +} + +impl FromStr for Prerelease { + type Err = Error; + + fn from_str(text: &str) -> Result<Self, Self::Err> { + let (pre, rest) = prerelease_identifier(text)?; + if !rest.is_empty() { + return Err(Error::new(ErrorKind::IllegalCharacter(Position::Pre))); + } + Ok(pre) + } +} + +impl FromStr for BuildMetadata { + type Err = Error; + + fn from_str(text: &str) -> Result<Self, Self::Err> { + let (build, rest) = build_identifier(text)?; + if !rest.is_empty() { + return Err(Error::new(ErrorKind::IllegalCharacter(Position::Build))); + } + Ok(build) + } +} + +impl Error { + fn new(kind: ErrorKind) -> Self { + Error { kind } + } +} + +impl Op { + const DEFAULT: Self = Op::Caret; +} + +fn numeric_identifier(input: &str, pos: Position) -> Result<(u64, &str), Error> { + let mut len = 0; + let mut value = 0u64; + + while let Some(&digit) = input.as_bytes().get(len) { + if digit < b'0' || digit > b'9' { + break; + } + if value == 0 && len > 0 { + return Err(Error::new(ErrorKind::LeadingZero(pos))); + } + match value + .checked_mul(10) + .and_then(|value| value.checked_add((digit - b'0') as u64)) + { + Some(sum) => value = sum, + None => return Err(Error::new(ErrorKind::Overflow(pos))), + } + len += 1; + } + + if len > 0 { + Ok((value, &input[len..])) + } else if let Some(unexpected) = input[len..].chars().next() { + Err(Error::new(ErrorKind::UnexpectedChar(pos, unexpected))) + } else { + Err(Error::new(ErrorKind::UnexpectedEnd(pos))) + } +} + +fn wildcard(input: &str) -> Option<(char, &str)> { + if let Some(rest) = input.strip_prefix('*') { + Some(('*', rest)) + } else if let Some(rest) = input.strip_prefix('x') { + Some(('x', rest)) + } else if let Some(rest) = input.strip_prefix('X') { + Some(('X', rest)) + } else { + None + } +} + +fn dot(input: &str, pos: Position) -> Result<&str, Error> { + if let Some(rest) = input.strip_prefix('.') { + Ok(rest) + } else if let Some(unexpected) = input.chars().next() { + Err(Error::new(ErrorKind::UnexpectedCharAfter(pos, unexpected))) + } else { + Err(Error::new(ErrorKind::UnexpectedEnd(pos))) + } +} + +fn prerelease_identifier(input: &str) -> Result<(Prerelease, &str), Error> { + let (string, rest) = identifier(input, Position::Pre)?; + let identifier = unsafe { Identifier::new_unchecked(string) }; + Ok((Prerelease { identifier }, rest)) +} + +fn build_identifier(input: &str) -> Result<(BuildMetadata, &str), Error> { + let (string, rest) = identifier(input, Position::Build)?; + let identifier = unsafe { Identifier::new_unchecked(string) }; + Ok((BuildMetadata { identifier }, rest)) +} + +fn identifier(input: &str, pos: Position) -> Result<(&str, &str), Error> { + let mut accumulated_len = 0; + let mut segment_len = 0; + let mut segment_has_nondigit = false; + + loop { + match input.as_bytes().get(accumulated_len + segment_len) { + Some(b'A'..=b'Z') | Some(b'a'..=b'z') | Some(b'-') => { + segment_len += 1; + segment_has_nondigit = true; + } + Some(b'0'..=b'9') => { + segment_len += 1; + } + boundary => { + if segment_len == 0 { + if accumulated_len == 0 && boundary != Some(&b'.') { + return Ok(("", input)); + } else { + return Err(Error::new(ErrorKind::EmptySegment(pos))); + } + } + if pos == Position::Pre + && segment_len > 1 + && !segment_has_nondigit + && input[accumulated_len..].starts_with('0') + { + return Err(Error::new(ErrorKind::LeadingZero(pos))); + } + accumulated_len += segment_len; + if boundary == Some(&b'.') { + accumulated_len += 1; + segment_len = 0; + segment_has_nondigit = false; + } else { + return Ok(input.split_at(accumulated_len)); + } + } + } + } +} + +fn op(input: &str) -> (Op, &str) { + let bytes = input.as_bytes(); + if bytes.first() == Some(&b'=') { + (Op::Exact, &input[1..]) + } else if bytes.first() == Some(&b'>') { + if bytes.get(1) == Some(&b'=') { + (Op::GreaterEq, &input[2..]) + } else { + (Op::Greater, &input[1..]) + } + } else if bytes.first() == Some(&b'<') { + if bytes.get(1) == Some(&b'=') { + (Op::LessEq, &input[2..]) + } else { + (Op::Less, &input[1..]) + } + } else if bytes.first() == Some(&b'~') { + (Op::Tilde, &input[1..]) + } else if bytes.first() == Some(&b'^') { + (Op::Caret, &input[1..]) + } else { + (Op::DEFAULT, input) + } +} + +fn comparator(input: &str) -> Result<(Comparator, Position, &str), Error> { + let (mut op, text) = op(input); + let default_op = input.len() == text.len(); + let text = text.trim_start_matches(' '); + + let mut pos = Position::Major; + let (major, text) = numeric_identifier(text, pos)?; + let mut has_wildcard = false; + + let (minor, text) = if let Some(text) = text.strip_prefix('.') { + pos = Position::Minor; + if let Some((_, text)) = wildcard(text) { + has_wildcard = true; + if default_op { + op = Op::Wildcard; + } + (None, text) + } else { + let (minor, text) = numeric_identifier(text, pos)?; + (Some(minor), text) + } + } else { + (None, text) + }; + + let (patch, text) = if let Some(text) = text.strip_prefix('.') { + pos = Position::Patch; + if let Some((_, text)) = wildcard(text) { + if default_op { + op = Op::Wildcard; + } + (None, text) + } else if has_wildcard { + return Err(Error::new(ErrorKind::UnexpectedAfterWildcard)); + } else { + let (patch, text) = numeric_identifier(text, pos)?; + (Some(patch), text) + } + } else { + (None, text) + }; + + let (pre, text) = if patch.is_some() && text.starts_with('-') { + pos = Position::Pre; + let text = &text[1..]; + let (pre, text) = prerelease_identifier(text)?; + if pre.is_empty() { + return Err(Error::new(ErrorKind::EmptySegment(pos))); + } + (pre, text) + } else { + (Prerelease::EMPTY, text) + }; + + let text = if patch.is_some() && text.starts_with('+') { + pos = Position::Build; + let text = &text[1..]; + let (build, text) = build_identifier(text)?; + if build.is_empty() { + return Err(Error::new(ErrorKind::EmptySegment(pos))); + } + text + } else { + text + }; + + let text = text.trim_start_matches(' '); + + let comparator = Comparator { + op, + major, + minor, + patch, + pre, + }; + + Ok((comparator, pos, text)) +} + +fn version_req(input: &str, out: &mut Vec<Comparator>, depth: usize) -> Result<usize, Error> { + let (comparator, pos, text) = match comparator(input) { + Ok(success) => success, + Err(mut error) => { + if let Some((ch, mut rest)) = wildcard(input) { + rest = rest.trim_start_matches(' '); + if rest.is_empty() || rest.starts_with(',') { + error.kind = ErrorKind::WildcardNotTheOnlyComparator(ch); + } + } + return Err(error); + } + }; + + if text.is_empty() { + out.reserve_exact(depth + 1); + unsafe { out.as_mut_ptr().add(depth).write(comparator) } + return Ok(depth + 1); + } + + let text = if let Some(text) = text.strip_prefix(',') { + text.trim_start_matches(' ') + } else { + let unexpected = text.chars().next().unwrap(); + return Err(Error::new(ErrorKind::ExpectedCommaFound(pos, unexpected))); + }; + + const MAX_COMPARATORS: usize = 32; + if depth + 1 == MAX_COMPARATORS { + return Err(Error::new(ErrorKind::ExcessiveComparators)); + } + + // Recurse to collect parsed Comparator objects on the stack. We perform a + // single allocation to allocate exactly the right sized Vec only once the + // total number of comparators is known. + let len = version_req(text, out, depth + 1)?; + unsafe { out.as_mut_ptr().add(depth).write(comparator) } + Ok(len) +} diff --git a/third_party/rust/semver/src/serde.rs b/third_party/rust/semver/src/serde.rs new file mode 100644 index 0000000000..1fcc7d87f6 --- /dev/null +++ b/third_party/rust/semver/src/serde.rs @@ -0,0 +1,109 @@ +use crate::{Comparator, Version, VersionReq}; +use core::fmt; +use serde::de::{Deserialize, Deserializer, Error, Visitor}; +use serde::ser::{Serialize, Serializer}; + +impl Serialize for Version { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + serializer.collect_str(self) + } +} + +impl Serialize for VersionReq { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + serializer.collect_str(self) + } +} + +impl Serialize for Comparator { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + serializer.collect_str(self) + } +} + +impl<'de> Deserialize<'de> for Version { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + struct VersionVisitor; + + impl<'de> Visitor<'de> for VersionVisitor { + type Value = Version; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("semver version") + } + + fn visit_str<E>(self, string: &str) -> Result<Self::Value, E> + where + E: Error, + { + string.parse().map_err(Error::custom) + } + } + + deserializer.deserialize_str(VersionVisitor) + } +} + +impl<'de> Deserialize<'de> for VersionReq { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + struct VersionReqVisitor; + + impl<'de> Visitor<'de> for VersionReqVisitor { + type Value = VersionReq; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("semver version") + } + + fn visit_str<E>(self, string: &str) -> Result<Self::Value, E> + where + E: Error, + { + string.parse().map_err(Error::custom) + } + } + + deserializer.deserialize_str(VersionReqVisitor) + } +} + +impl<'de> Deserialize<'de> for Comparator { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + struct ComparatorVisitor; + + impl<'de> Visitor<'de> for ComparatorVisitor { + type Value = Comparator; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("semver comparator") + } + + fn visit_str<E>(self, string: &str) -> Result<Self::Value, E> + where + E: Error, + { + string.parse().map_err(Error::custom) + } + } + + deserializer.deserialize_str(ComparatorVisitor) + } +} diff --git a/third_party/rust/semver/tests/node/mod.rs b/third_party/rust/semver/tests/node/mod.rs new file mode 100644 index 0000000000..eb50673d5f --- /dev/null +++ b/third_party/rust/semver/tests/node/mod.rs @@ -0,0 +1,43 @@ +#![cfg(test_node_semver)] + +use semver::Version; +use std::fmt::{self, Display}; +use std::process::Command; + +#[derive(Default, Eq, PartialEq, Hash, Debug)] +pub(super) struct VersionReq(semver::VersionReq); + +impl VersionReq { + pub(super) const STAR: Self = VersionReq(semver::VersionReq::STAR); + + pub(super) fn matches(&self, version: &Version) -> bool { + let out = Command::new("node") + .arg("-e") + .arg(format!( + "console.log(require('semver').satisfies('{}', '{}'))", + version, + self.to_string().replace(',', ""), + )) + .output() + .unwrap(); + if out.stdout == b"true\n" { + true + } else if out.stdout == b"false\n" { + false + } else { + let s = String::from_utf8_lossy(&out.stdout) + String::from_utf8_lossy(&out.stderr); + panic!("unexpected output: {}", s); + } + } +} + +impl Display for VersionReq { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + Display::fmt(&self.0, formatter) + } +} + +#[cfg_attr(not(no_track_caller), track_caller)] +pub(super) fn req(text: &str) -> VersionReq { + VersionReq(crate::util::req(text)) +} diff --git a/third_party/rust/semver/tests/test_autotrait.rs b/third_party/rust/semver/tests/test_autotrait.rs new file mode 100644 index 0000000000..af8534bd73 --- /dev/null +++ b/third_party/rust/semver/tests/test_autotrait.rs @@ -0,0 +1,12 @@ +fn assert_send_sync<T: Send + Sync>() {} + +#[test] +fn test() { + assert_send_sync::<semver::BuildMetadata>(); + assert_send_sync::<semver::Comparator>(); + assert_send_sync::<semver::Error>(); + assert_send_sync::<semver::Prerelease>(); + assert_send_sync::<semver::Version>(); + assert_send_sync::<semver::VersionReq>(); + assert_send_sync::<semver::Op>(); +} diff --git a/third_party/rust/semver/tests/test_identifier.rs b/third_party/rust/semver/tests/test_identifier.rs new file mode 100644 index 0000000000..dc888c9c10 --- /dev/null +++ b/third_party/rust/semver/tests/test_identifier.rs @@ -0,0 +1,45 @@ +#![allow( + clippy::eq_op, + clippy::needless_pass_by_value, + clippy::toplevel_ref_arg, + clippy::wildcard_imports +)] + +mod util; + +use crate::util::*; +use semver::Prerelease; + +#[test] +fn test_new() { + fn test(identifier: Prerelease, expected: &str) { + assert_eq!(identifier.is_empty(), expected.is_empty()); + assert_eq!(identifier.len(), expected.len()); + assert_eq!(identifier.as_str(), expected); + assert_eq!(identifier, identifier); + assert_eq!(identifier, identifier.clone()); + } + + let ref mut string = String::new(); + let limit = if cfg!(miri) { 40 } else { 280 }; // miri is slow + for _ in 0..limit { + test(prerelease(string), string); + string.push('1'); + } + + if !cfg!(miri) { + let ref string = string.repeat(20000); + test(prerelease(string), string); + } +} + +#[test] +fn test_eq() { + assert_eq!(prerelease("-"), prerelease("-")); + assert_ne!(prerelease("a"), prerelease("aa")); + assert_ne!(prerelease("aa"), prerelease("a")); + assert_ne!(prerelease("aaaaaaaaa"), prerelease("a")); + assert_ne!(prerelease("a"), prerelease("aaaaaaaaa")); + assert_ne!(prerelease("aaaaaaaaa"), prerelease("bbbbbbbbb")); + assert_ne!(build_metadata("1"), build_metadata("001")); +} diff --git a/third_party/rust/semver/tests/test_version.rs b/third_party/rust/semver/tests/test_version.rs new file mode 100644 index 0000000000..93a528c193 --- /dev/null +++ b/third_party/rust/semver/tests/test_version.rs @@ -0,0 +1,241 @@ +#![allow( + clippy::nonminimal_bool, + clippy::too_many_lines, + clippy::wildcard_imports +)] + +mod util; + +use crate::util::*; +use semver::{BuildMetadata, Prerelease, Version}; + +#[test] +fn test_parse() { + let err = version_err(""); + assert_to_string( + err, + "unexpected end of input while parsing major version number", + ); + + let err = version_err(" "); + assert_to_string( + err, + "unexpected character ' ' while parsing major version number", + ); + + let err = version_err("1"); + assert_to_string( + err, + "unexpected end of input while parsing major version number", + ); + + let err = version_err("1.2"); + assert_to_string( + err, + "unexpected end of input while parsing minor version number", + ); + + let err = version_err("1.2.3-"); + assert_to_string(err, "empty identifier segment in pre-release identifier"); + + let err = version_err("a.b.c"); + assert_to_string( + err, + "unexpected character 'a' while parsing major version number", + ); + + let err = version_err("1.2.3 abc"); + assert_to_string(err, "unexpected character ' ' after patch version number"); + + let err = version_err("1.2.3-01"); + assert_to_string(err, "invalid leading zero in pre-release identifier"); + + let parsed = version("1.2.3"); + let expected = Version::new(1, 2, 3); + assert_eq!(parsed, expected); + let expected = Version { + major: 1, + minor: 2, + patch: 3, + pre: Prerelease::EMPTY, + build: BuildMetadata::EMPTY, + }; + assert_eq!(parsed, expected); + + let parsed = version("1.2.3-alpha1"); + let expected = Version { + major: 1, + minor: 2, + patch: 3, + pre: prerelease("alpha1"), + build: BuildMetadata::EMPTY, + }; + assert_eq!(parsed, expected); + + let parsed = version("1.2.3+build5"); + let expected = Version { + major: 1, + minor: 2, + patch: 3, + pre: Prerelease::EMPTY, + build: build_metadata("build5"), + }; + assert_eq!(parsed, expected); + + let parsed = version("1.2.3+5build"); + let expected = Version { + major: 1, + minor: 2, + patch: 3, + pre: Prerelease::EMPTY, + build: build_metadata("5build"), + }; + assert_eq!(parsed, expected); + + let parsed = version("1.2.3-alpha1+build5"); + let expected = Version { + major: 1, + minor: 2, + patch: 3, + pre: prerelease("alpha1"), + build: build_metadata("build5"), + }; + assert_eq!(parsed, expected); + + let parsed = version("1.2.3-1.alpha1.9+build5.7.3aedf"); + let expected = Version { + major: 1, + minor: 2, + patch: 3, + pre: prerelease("1.alpha1.9"), + build: build_metadata("build5.7.3aedf"), + }; + assert_eq!(parsed, expected); + + let parsed = version("1.2.3-0a.alpha1.9+05build.7.3aedf"); + let expected = Version { + major: 1, + minor: 2, + patch: 3, + pre: prerelease("0a.alpha1.9"), + build: build_metadata("05build.7.3aedf"), + }; + assert_eq!(parsed, expected); + + let parsed = version("0.4.0-beta.1+0851523"); + let expected = Version { + major: 0, + minor: 4, + patch: 0, + pre: prerelease("beta.1"), + build: build_metadata("0851523"), + }; + assert_eq!(parsed, expected); + + // for https://nodejs.org/dist/index.json, where some older npm versions are "1.1.0-beta-10" + let parsed = version("1.1.0-beta-10"); + let expected = Version { + major: 1, + minor: 1, + patch: 0, + pre: prerelease("beta-10"), + build: BuildMetadata::EMPTY, + }; + assert_eq!(parsed, expected); +} + +#[test] +fn test_eq() { + assert_eq!(version("1.2.3"), version("1.2.3")); + assert_eq!(version("1.2.3-alpha1"), version("1.2.3-alpha1")); + assert_eq!(version("1.2.3+build.42"), version("1.2.3+build.42")); + assert_eq!(version("1.2.3-alpha1+42"), version("1.2.3-alpha1+42")); +} + +#[test] +fn test_ne() { + assert_ne!(version("0.0.0"), version("0.0.1")); + assert_ne!(version("0.0.0"), version("0.1.0")); + assert_ne!(version("0.0.0"), version("1.0.0")); + assert_ne!(version("1.2.3-alpha"), version("1.2.3-beta")); + assert_ne!(version("1.2.3+23"), version("1.2.3+42")); +} + +#[test] +fn test_display() { + assert_to_string(version("1.2.3"), "1.2.3"); + assert_to_string(version("1.2.3-alpha1"), "1.2.3-alpha1"); + assert_to_string(version("1.2.3+build.42"), "1.2.3+build.42"); + assert_to_string(version("1.2.3-alpha1+42"), "1.2.3-alpha1+42"); +} + +#[test] +fn test_lt() { + assert!(version("0.0.0") < version("1.2.3-alpha2")); + assert!(version("1.0.0") < version("1.2.3-alpha2")); + assert!(version("1.2.0") < version("1.2.3-alpha2")); + assert!(version("1.2.3-alpha1") < version("1.2.3")); + assert!(version("1.2.3-alpha1") < version("1.2.3-alpha2")); + assert!(!(version("1.2.3-alpha2") < version("1.2.3-alpha2"))); + assert!(version("1.2.3+23") < version("1.2.3+42")); +} + +#[test] +fn test_le() { + assert!(version("0.0.0") <= version("1.2.3-alpha2")); + assert!(version("1.0.0") <= version("1.2.3-alpha2")); + assert!(version("1.2.0") <= version("1.2.3-alpha2")); + assert!(version("1.2.3-alpha1") <= version("1.2.3-alpha2")); + assert!(version("1.2.3-alpha2") <= version("1.2.3-alpha2")); + assert!(version("1.2.3+23") <= version("1.2.3+42")); +} + +#[test] +fn test_gt() { + assert!(version("1.2.3-alpha2") > version("0.0.0")); + assert!(version("1.2.3-alpha2") > version("1.0.0")); + assert!(version("1.2.3-alpha2") > version("1.2.0")); + assert!(version("1.2.3-alpha2") > version("1.2.3-alpha1")); + assert!(version("1.2.3") > version("1.2.3-alpha2")); + assert!(!(version("1.2.3-alpha2") > version("1.2.3-alpha2"))); + assert!(!(version("1.2.3+23") > version("1.2.3+42"))); +} + +#[test] +fn test_ge() { + assert!(version("1.2.3-alpha2") >= version("0.0.0")); + assert!(version("1.2.3-alpha2") >= version("1.0.0")); + assert!(version("1.2.3-alpha2") >= version("1.2.0")); + assert!(version("1.2.3-alpha2") >= version("1.2.3-alpha1")); + assert!(version("1.2.3-alpha2") >= version("1.2.3-alpha2")); + assert!(!(version("1.2.3+23") >= version("1.2.3+42"))); +} + +#[test] +fn test_spec_order() { + let vs = [ + "1.0.0-alpha", + "1.0.0-alpha.1", + "1.0.0-alpha.beta", + "1.0.0-beta", + "1.0.0-beta.2", + "1.0.0-beta.11", + "1.0.0-rc.1", + "1.0.0", + ]; + let mut i = 1; + while i < vs.len() { + let a = version(vs[i - 1]); + let b = version(vs[i]); + assert!(a < b, "nope {:?} < {:?}", a, b); + i += 1; + } +} + +#[test] +fn test_align() { + let version = version("1.2.3-rc1"); + assert_eq!("1.2.3-rc1 ", format!("{:20}", version)); + assert_eq!("*****1.2.3-rc1******", format!("{:*^20}", version)); + assert_eq!(" 1.2.3-rc1", format!("{:>20}", version)); +} diff --git a/third_party/rust/semver/tests/test_version_req.rs b/third_party/rust/semver/tests/test_version_req.rs new file mode 100644 index 0000000000..98a03ac896 --- /dev/null +++ b/third_party/rust/semver/tests/test_version_req.rs @@ -0,0 +1,443 @@ +#![allow( + clippy::missing_panics_doc, + clippy::shadow_unrelated, + clippy::toplevel_ref_arg, + clippy::wildcard_imports +)] + +mod node; +mod util; + +use crate::util::*; +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; + +#[cfg(test_node_semver)] +use node::{req, VersionReq}; +#[cfg(not(test_node_semver))] +use semver::VersionReq; + +#[cfg_attr(not(no_track_caller), track_caller)] +fn assert_match_all(req: &VersionReq, versions: &[&str]) { + for string in versions { + let parsed = version(string); + assert!(req.matches(&parsed), "did not match {}", string); + } +} + +#[cfg_attr(not(no_track_caller), track_caller)] +fn assert_match_none(req: &VersionReq, versions: &[&str]) { + for string in versions { + let parsed = version(string); + assert!(!req.matches(&parsed), "matched {}", string); + } +} + +#[test] +fn test_basic() { + let ref r = req("1.0.0"); + assert_to_string(r, "^1.0.0"); + assert_match_all(r, &["1.0.0", "1.1.0", "1.0.1"]); + assert_match_none(r, &["0.9.9", "0.10.0", "0.1.0", "1.0.0-pre", "1.0.1-pre"]); +} + +#[test] +#[cfg(not(no_const_vec_new))] +fn test_default() { + let ref r = VersionReq::default(); + assert_eq!(r, &VersionReq::STAR); +} + +#[test] +fn test_exact() { + let ref r = req("=1.0.0"); + assert_to_string(r, "=1.0.0"); + assert_match_all(r, &["1.0.0"]); + assert_match_none(r, &["1.0.1", "0.9.9", "0.10.0", "0.1.0", "1.0.0-pre"]); + + let ref r = req("=0.9.0"); + assert_to_string(r, "=0.9.0"); + assert_match_all(r, &["0.9.0"]); + assert_match_none(r, &["0.9.1", "1.9.0", "0.0.9", "0.9.0-pre"]); + + let ref r = req("=0.0.2"); + assert_to_string(r, "=0.0.2"); + assert_match_all(r, &["0.0.2"]); + assert_match_none(r, &["0.0.1", "0.0.3", "0.0.2-pre"]); + + let ref r = req("=0.1.0-beta2.a"); + assert_to_string(r, "=0.1.0-beta2.a"); + assert_match_all(r, &["0.1.0-beta2.a"]); + assert_match_none(r, &["0.9.1", "0.1.0", "0.1.1-beta2.a", "0.1.0-beta2"]); + + let ref r = req("=0.1.0+meta"); + assert_to_string(r, "=0.1.0"); + assert_match_all(r, &["0.1.0", "0.1.0+meta", "0.1.0+any"]); +} + +#[test] +pub fn test_greater_than() { + let ref r = req(">= 1.0.0"); + assert_to_string(r, ">=1.0.0"); + assert_match_all(r, &["1.0.0", "2.0.0"]); + assert_match_none(r, &["0.1.0", "0.0.1", "1.0.0-pre", "2.0.0-pre"]); + + let ref r = req(">= 2.1.0-alpha2"); + assert_to_string(r, ">=2.1.0-alpha2"); + assert_match_all(r, &["2.1.0-alpha2", "2.1.0-alpha3", "2.1.0", "3.0.0"]); + assert_match_none( + r, + &["2.0.0", "2.1.0-alpha1", "2.0.0-alpha2", "3.0.0-alpha2"], + ); +} + +#[test] +pub fn test_less_than() { + let ref r = req("< 1.0.0"); + assert_to_string(r, "<1.0.0"); + assert_match_all(r, &["0.1.0", "0.0.1"]); + assert_match_none(r, &["1.0.0", "1.0.0-beta", "1.0.1", "0.9.9-alpha"]); + + let ref r = req("<= 2.1.0-alpha2"); + assert_match_all(r, &["2.1.0-alpha2", "2.1.0-alpha1", "2.0.0", "1.0.0"]); + assert_match_none( + r, + &["2.1.0", "2.2.0-alpha1", "2.0.0-alpha2", "1.0.0-alpha2"], + ); + + let ref r = req(">1.0.0-alpha, <1.0.0"); + assert_match_all(r, &["1.0.0-beta"]); + + let ref r = req(">1.0.0-alpha, <1.0"); + assert_match_none(r, &["1.0.0-beta"]); + + let ref r = req(">1.0.0-alpha, <1"); + assert_match_none(r, &["1.0.0-beta"]); +} + +#[test] +pub fn test_multiple() { + let ref r = req("> 0.0.9, <= 2.5.3"); + assert_to_string(r, ">0.0.9, <=2.5.3"); + assert_match_all(r, &["0.0.10", "1.0.0", "2.5.3"]); + assert_match_none(r, &["0.0.8", "2.5.4"]); + + let ref r = req("0.3.0, 0.4.0"); + assert_to_string(r, "^0.3.0, ^0.4.0"); + assert_match_none(r, &["0.0.8", "0.3.0", "0.4.0"]); + + let ref r = req("<= 0.2.0, >= 0.5.0"); + assert_to_string(r, "<=0.2.0, >=0.5.0"); + assert_match_none(r, &["0.0.8", "0.3.0", "0.5.1"]); + + let ref r = req("0.1.0, 0.1.4, 0.1.6"); + assert_to_string(r, "^0.1.0, ^0.1.4, ^0.1.6"); + assert_match_all(r, &["0.1.6", "0.1.9"]); + assert_match_none(r, &["0.1.0", "0.1.4", "0.2.0"]); + + let err = req_err("> 0.1.0,"); + assert_to_string( + err, + "unexpected end of input while parsing major version number", + ); + + let err = req_err("> 0.3.0, ,"); + assert_to_string( + err, + "unexpected character ',' while parsing major version number", + ); + + let ref r = req(">=0.5.1-alpha3, <0.6"); + assert_to_string(r, ">=0.5.1-alpha3, <0.6"); + assert_match_all( + r, + &[ + "0.5.1-alpha3", + "0.5.1-alpha4", + "0.5.1-beta", + "0.5.1", + "0.5.5", + ], + ); + assert_match_none( + r, + &["0.5.1-alpha1", "0.5.2-alpha3", "0.5.5-pre", "0.5.0-pre"], + ); + assert_match_none(r, &["0.6.0", "0.6.0-pre"]); + + // https://github.com/steveklabnik/semver/issues/56 + let err = req_err("1.2.3 - 2.3.4"); + assert_to_string(err, "expected comma after patch version number, found '-'"); +} + +#[test] +pub fn test_whitespace_delimited_comparator_sets() { + // https://github.com/steveklabnik/semver/issues/55 + let err = req_err("> 0.0.9 <= 2.5.3"); + assert_to_string(err, "expected comma after patch version number, found '<'"); +} + +#[test] +pub fn test_tilde() { + let ref r = req("~1"); + assert_match_all(r, &["1.0.0", "1.0.1", "1.1.1"]); + assert_match_none(r, &["0.9.1", "2.9.0", "0.0.9"]); + + let ref r = req("~1.2"); + assert_match_all(r, &["1.2.0", "1.2.1"]); + assert_match_none(r, &["1.1.1", "1.3.0", "0.0.9"]); + + let ref r = req("~1.2.2"); + assert_match_all(r, &["1.2.2", "1.2.4"]); + assert_match_none(r, &["1.2.1", "1.9.0", "1.0.9", "2.0.1", "0.1.3"]); + + let ref r = req("~1.2.3-beta.2"); + assert_match_all(r, &["1.2.3", "1.2.4", "1.2.3-beta.2", "1.2.3-beta.4"]); + assert_match_none(r, &["1.3.3", "1.1.4", "1.2.3-beta.1", "1.2.4-beta.2"]); +} + +#[test] +pub fn test_caret() { + let ref r = req("^1"); + assert_match_all(r, &["1.1.2", "1.1.0", "1.2.1", "1.0.1"]); + assert_match_none(r, &["0.9.1", "2.9.0", "0.1.4"]); + assert_match_none(r, &["1.0.0-beta1", "0.1.0-alpha", "1.0.1-pre"]); + + let ref r = req("^1.1"); + assert_match_all(r, &["1.1.2", "1.1.0", "1.2.1"]); + assert_match_none(r, &["0.9.1", "2.9.0", "1.0.1", "0.1.4"]); + + let ref r = req("^1.1.2"); + assert_match_all(r, &["1.1.2", "1.1.4", "1.2.1"]); + assert_match_none(r, &["0.9.1", "2.9.0", "1.1.1", "0.0.1"]); + assert_match_none(r, &["1.1.2-alpha1", "1.1.3-alpha1", "2.9.0-alpha1"]); + + let ref r = req("^0.1.2"); + assert_match_all(r, &["0.1.2", "0.1.4"]); + assert_match_none(r, &["0.9.1", "2.9.0", "1.1.1", "0.0.1"]); + assert_match_none(r, &["0.1.2-beta", "0.1.3-alpha", "0.2.0-pre"]); + + let ref r = req("^0.5.1-alpha3"); + assert_match_all( + r, + &[ + "0.5.1-alpha3", + "0.5.1-alpha4", + "0.5.1-beta", + "0.5.1", + "0.5.5", + ], + ); + assert_match_none( + r, + &[ + "0.5.1-alpha1", + "0.5.2-alpha3", + "0.5.5-pre", + "0.5.0-pre", + "0.6.0", + ], + ); + + let ref r = req("^0.0.2"); + assert_match_all(r, &["0.0.2"]); + assert_match_none(r, &["0.9.1", "2.9.0", "1.1.1", "0.0.1", "0.1.4"]); + + let ref r = req("^0.0"); + assert_match_all(r, &["0.0.2", "0.0.0"]); + assert_match_none(r, &["0.9.1", "2.9.0", "1.1.1", "0.1.4"]); + + let ref r = req("^0"); + assert_match_all(r, &["0.9.1", "0.0.2", "0.0.0"]); + assert_match_none(r, &["2.9.0", "1.1.1"]); + + let ref r = req("^1.4.2-beta.5"); + assert_match_all( + r, + &["1.4.2", "1.4.3", "1.4.2-beta.5", "1.4.2-beta.6", "1.4.2-c"], + ); + assert_match_none( + r, + &[ + "0.9.9", + "2.0.0", + "1.4.2-alpha", + "1.4.2-beta.4", + "1.4.3-beta.5", + ], + ); +} + +#[test] +pub fn test_wildcard() { + let err = req_err(""); + assert_to_string( + err, + "unexpected end of input while parsing major version number", + ); + + let ref r = req("*"); + assert_match_all(r, &["0.9.1", "2.9.0", "0.0.9", "1.0.1", "1.1.1"]); + assert_match_none(r, &["1.0.0-pre"]); + + for s in &["x", "X"] { + assert_eq!(*r, req(s)); + } + + let ref r = req("1.*"); + assert_match_all(r, &["1.2.0", "1.2.1", "1.1.1", "1.3.0"]); + assert_match_none(r, &["0.0.9", "1.2.0-pre"]); + + for s in &["1.x", "1.X", "1.*.*"] { + assert_eq!(*r, req(s)); + } + + let ref r = req("1.2.*"); + assert_match_all(r, &["1.2.0", "1.2.2", "1.2.4"]); + assert_match_none(r, &["1.9.0", "1.0.9", "2.0.1", "0.1.3", "1.2.2-pre"]); + + for s in &["1.2.x", "1.2.X"] { + assert_eq!(*r, req(s)); + } +} + +#[test] +pub fn test_logical_or() { + // https://github.com/steveklabnik/semver/issues/57 + let err = req_err("=1.2.3 || =2.3.4"); + assert_to_string(err, "expected comma after patch version number, found '|'"); + + let err = req_err("1.1 || =1.2.3"); + assert_to_string(err, "expected comma after minor version number, found '|'"); + + let err = req_err("6.* || 8.* || >= 10.*"); + assert_to_string(err, "expected comma after minor version number, found '|'"); +} + +#[test] +pub fn test_any() { + #[cfg(not(no_const_vec_new))] + let ref r = VersionReq::STAR; + #[cfg(no_const_vec_new)] + let ref r = VersionReq { + comparators: Vec::new(), + }; + assert_match_all(r, &["0.0.1", "0.1.0", "1.0.0"]); +} + +#[test] +pub fn test_pre() { + let ref r = req("=2.1.1-really.0"); + assert_match_all(r, &["2.1.1-really.0"]); +} + +#[test] +pub fn test_parse_errors() { + let err = req_err("\0"); + assert_to_string( + err, + "unexpected character '\\0' while parsing major version number", + ); + + let err = req_err(">= >= 0.0.2"); + assert_to_string( + err, + "unexpected character '>' while parsing major version number", + ); + + let err = req_err(">== 0.0.2"); + assert_to_string( + err, + "unexpected character '=' while parsing major version number", + ); + + let err = req_err("a.0.0"); + assert_to_string( + err, + "unexpected character 'a' while parsing major version number", + ); + + let err = req_err("1.0.0-"); + assert_to_string(err, "empty identifier segment in pre-release identifier"); + + let err = req_err(">="); + assert_to_string( + err, + "unexpected end of input while parsing major version number", + ); +} + +#[test] +fn test_cargo3202() { + let ref r = req("0.*.*"); + assert_to_string(r, "0.*"); + assert_match_all(r, &["0.5.0"]); + + let ref r = req("0.0.*"); + assert_to_string(r, "0.0.*"); +} + +#[test] +fn test_digit_after_wildcard() { + let err = req_err("*.1"); + assert_to_string(err, "unexpected character after wildcard in version req"); + + let err = req_err("1.*.1"); + assert_to_string(err, "unexpected character after wildcard in version req"); + + let err = req_err(">=1.*.1"); + assert_to_string(err, "unexpected character after wildcard in version req"); +} + +#[test] +fn test_eq_hash() { + fn calculate_hash(value: impl Hash) -> u64 { + let mut hasher = DefaultHasher::new(); + value.hash(&mut hasher); + hasher.finish() + } + + assert!(req("^1") == req("^1")); + assert!(calculate_hash(req("^1")) == calculate_hash(req("^1"))); + assert!(req("^1") != req("^2")); +} + +#[test] +fn test_leading_digit_in_pre_and_build() { + for op in &["=", ">", ">=", "<", "<=", "~", "^"] { + // digit then alpha + req(&format!("{} 1.2.3-1a", op)); + req(&format!("{} 1.2.3+1a", op)); + + // digit then alpha (leading zero) + req(&format!("{} 1.2.3-01a", op)); + req(&format!("{} 1.2.3+01", op)); + + // multiple + req(&format!("{} 1.2.3-1+1", op)); + req(&format!("{} 1.2.3-1-1+1-1-1", op)); + req(&format!("{} 1.2.3-1a+1a", op)); + req(&format!("{} 1.2.3-1a-1a+1a-1a-1a", op)); + } +} + +#[test] +fn test_wildcard_and_another() { + let err = req_err("*, 0.20.0-any"); + assert_to_string( + err, + "wildcard req (*) must be the only comparator in the version req", + ); + + let err = req_err("0.20.0-any, *"); + assert_to_string( + err, + "wildcard req (*) must be the only comparator in the version req", + ); + + let err = req_err("0.20.0-any, *, 1.0"); + assert_to_string( + err, + "wildcard req (*) must be the only comparator in the version req", + ); +} diff --git a/third_party/rust/semver/tests/util/mod.rs b/third_party/rust/semver/tests/util/mod.rs new file mode 100644 index 0000000000..5cc142c484 --- /dev/null +++ b/third_party/rust/semver/tests/util/mod.rs @@ -0,0 +1,39 @@ +#![allow(dead_code)] + +use semver::{BuildMetadata, Error, Prerelease, Version, VersionReq}; +use std::fmt::Display; + +#[cfg_attr(not(no_track_caller), track_caller)] +pub(super) fn version(text: &str) -> Version { + Version::parse(text).unwrap() +} + +#[cfg_attr(not(no_track_caller), track_caller)] +pub(super) fn version_err(text: &str) -> Error { + Version::parse(text).unwrap_err() +} + +#[cfg_attr(not(no_track_caller), track_caller)] +pub(super) fn req(text: &str) -> VersionReq { + VersionReq::parse(text).unwrap() +} + +#[cfg_attr(not(no_track_caller), track_caller)] +pub(super) fn req_err(text: &str) -> Error { + VersionReq::parse(text).unwrap_err() +} + +#[cfg_attr(not(no_track_caller), track_caller)] +pub(super) fn prerelease(text: &str) -> Prerelease { + Prerelease::new(text).unwrap() +} + +#[cfg_attr(not(no_track_caller), track_caller)] +pub(super) fn build_metadata(text: &str) -> BuildMetadata { + BuildMetadata::new(text).unwrap() +} + +#[cfg_attr(not(no_track_caller), track_caller)] +pub(super) fn assert_to_string(value: impl Display, expected: &str) { + assert_eq!(value.to_string(), expected); +} |