Add new remain-0.2.1 am: 3095d1536d am: 4a2168c525 am: 822c31315f am: a3f8df85a9
Change-Id: Id2a1ecdd86527afca2ebb6c0c6f5efe91b485f1e
diff --git a/.cargo_vcs_info.json b/0.1.3/.cargo_vcs_info.json
similarity index 100%
rename from .cargo_vcs_info.json
rename to 0.1.3/.cargo_vcs_info.json
diff --git a/.gitignore b/0.1.3/.gitignore
similarity index 100%
rename from .gitignore
rename to 0.1.3/.gitignore
diff --git a/.travis.yml b/0.1.3/.travis.yml
similarity index 100%
rename from .travis.yml
rename to 0.1.3/.travis.yml
diff --git a/0.1.3/Android.bp b/0.1.3/Android.bp
new file mode 100644
index 0000000..5fa586f
--- /dev/null
+++ b/0.1.3/Android.bp
@@ -0,0 +1,13 @@
+// This file is generated by cargo2android.py.
+
+rust_proc_macro {
+ name: "libremain-0.1.3",
+ crate_name: "remain",
+ srcs: ["src/lib.rs"],
+ edition: "2018",
+ rlibs: [
+ "libproc_macro2",
+ "libquote",
+ "libsyn-0.15.42",
+ ],
+}
diff --git a/Cargo.toml b/0.1.3/Cargo.toml
similarity index 100%
rename from Cargo.toml
rename to 0.1.3/Cargo.toml
diff --git a/Cargo.toml.orig b/0.1.3/Cargo.toml.orig
similarity index 100%
rename from Cargo.toml.orig
rename to 0.1.3/Cargo.toml.orig
diff --git a/0.1.3/LICENSE b/0.1.3/LICENSE
new file mode 120000
index 0000000..6b579aa
--- /dev/null
+++ b/0.1.3/LICENSE
@@ -0,0 +1 @@
+LICENSE-APACHE
\ No newline at end of file
diff --git a/LICENSE-APACHE b/0.1.3/LICENSE-APACHE
similarity index 100%
rename from LICENSE-APACHE
rename to 0.1.3/LICENSE-APACHE
diff --git a/LICENSE-MIT b/0.1.3/LICENSE-MIT
similarity index 100%
rename from LICENSE-MIT
rename to 0.1.3/LICENSE-MIT
diff --git a/0.1.3/METADATA b/0.1.3/METADATA
new file mode 100644
index 0000000..26a274d
--- /dev/null
+++ b/0.1.3/METADATA
@@ -0,0 +1,18 @@
+name: "remain"
+description:
+ "This crate provides an attribute macro to check at compile time that the "
+ "variants of an enum or the arms of a match expression are written in "
+ "sorted order."
+
+third_party {
+ url {
+ type: HOMEPAGE
+ value: "https://crates.io/crates/remain"
+ }
+ url {
+ type: GIT
+ value: "https://github.com/dtolnay/remain"
+ }
+ version: "0.1.3"
+ last_upgrade_date { year: 2019 month: 6 day: 13 }
+}
diff --git a/0.1.3/MODULE_LICENSE_APACHE2 b/0.1.3/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/0.1.3/MODULE_LICENSE_APACHE2
diff --git a/0.1.3/NOTICE b/0.1.3/NOTICE
new file mode 120000
index 0000000..7a694c9
--- /dev/null
+++ b/0.1.3/NOTICE
@@ -0,0 +1 @@
+LICENSE
\ No newline at end of file
diff --git a/README.md b/0.1.3/README.md
similarity index 100%
rename from README.md
rename to 0.1.3/README.md
diff --git a/src/check.rs b/0.1.3/src/check.rs
similarity index 100%
rename from src/check.rs
rename to 0.1.3/src/check.rs
diff --git a/src/compare.rs b/0.1.3/src/compare.rs
similarity index 100%
rename from src/compare.rs
rename to 0.1.3/src/compare.rs
diff --git a/src/emit.rs b/0.1.3/src/emit.rs
similarity index 100%
rename from src/emit.rs
rename to 0.1.3/src/emit.rs
diff --git a/src/format.rs b/0.1.3/src/format.rs
similarity index 100%
rename from src/format.rs
rename to 0.1.3/src/format.rs
diff --git a/src/lib.rs b/0.1.3/src/lib.rs
similarity index 100%
rename from src/lib.rs
rename to 0.1.3/src/lib.rs
diff --git a/src/parse.rs b/0.1.3/src/parse.rs
similarity index 100%
rename from src/parse.rs
rename to 0.1.3/src/parse.rs
diff --git a/src/visit.rs b/0.1.3/src/visit.rs
similarity index 100%
rename from src/visit.rs
rename to 0.1.3/src/visit.rs
diff --git a/tests/compiletest.rs b/0.1.3/tests/compiletest.rs
similarity index 100%
rename from tests/compiletest.rs
rename to 0.1.3/tests/compiletest.rs
diff --git a/tests/stable.rs b/0.1.3/tests/stable.rs
similarity index 100%
rename from tests/stable.rs
rename to 0.1.3/tests/stable.rs
diff --git a/tests/ui/enum.rs b/0.1.3/tests/ui/enum.rs
similarity index 100%
rename from tests/ui/enum.rs
rename to 0.1.3/tests/ui/enum.rs
diff --git a/tests/ui/enum.stderr b/0.1.3/tests/ui/enum.stderr
similarity index 100%
rename from tests/ui/enum.stderr
rename to 0.1.3/tests/ui/enum.stderr
diff --git a/tests/ui/let-stable.rs b/0.1.3/tests/ui/let-stable.rs
similarity index 100%
rename from tests/ui/let-stable.rs
rename to 0.1.3/tests/ui/let-stable.rs
diff --git a/tests/ui/let-stable.stderr b/0.1.3/tests/ui/let-stable.stderr
similarity index 100%
rename from tests/ui/let-stable.stderr
rename to 0.1.3/tests/ui/let-stable.stderr
diff --git a/tests/ui/let-unstable.rs b/0.1.3/tests/ui/let-unstable.rs
similarity index 100%
rename from tests/ui/let-unstable.rs
rename to 0.1.3/tests/ui/let-unstable.rs
diff --git a/tests/ui/let-unstable.stderr b/0.1.3/tests/ui/let-unstable.stderr
similarity index 100%
rename from tests/ui/let-unstable.stderr
rename to 0.1.3/tests/ui/let-unstable.stderr
diff --git a/tests/ui/match-stable.rs b/0.1.3/tests/ui/match-stable.rs
similarity index 100%
rename from tests/ui/match-stable.rs
rename to 0.1.3/tests/ui/match-stable.rs
diff --git a/tests/ui/match-stable.stderr b/0.1.3/tests/ui/match-stable.stderr
similarity index 100%
rename from tests/ui/match-stable.stderr
rename to 0.1.3/tests/ui/match-stable.stderr
diff --git a/tests/ui/match-unstable.rs b/0.1.3/tests/ui/match-unstable.rs
similarity index 100%
rename from tests/ui/match-unstable.rs
rename to 0.1.3/tests/ui/match-unstable.rs
diff --git a/tests/ui/match-unstable.stderr b/0.1.3/tests/ui/match-unstable.stderr
similarity index 100%
rename from tests/ui/match-unstable.stderr
rename to 0.1.3/tests/ui/match-unstable.stderr
diff --git a/tests/ui/struct.rs b/0.1.3/tests/ui/struct.rs
similarity index 100%
rename from tests/ui/struct.rs
rename to 0.1.3/tests/ui/struct.rs
diff --git a/tests/ui/struct.stderr b/0.1.3/tests/ui/struct.stderr
similarity index 100%
rename from tests/ui/struct.stderr
rename to 0.1.3/tests/ui/struct.stderr
diff --git a/tests/ui/unnamed-fields.rs b/0.1.3/tests/ui/unnamed-fields.rs
similarity index 100%
rename from tests/ui/unnamed-fields.rs
rename to 0.1.3/tests/ui/unnamed-fields.rs
diff --git a/tests/ui/unnamed-fields.stderr b/0.1.3/tests/ui/unnamed-fields.stderr
similarity index 100%
rename from tests/ui/unnamed-fields.stderr
rename to 0.1.3/tests/ui/unnamed-fields.stderr
diff --git a/tests/ui/unsupported.rs b/0.1.3/tests/ui/unsupported.rs
similarity index 100%
rename from tests/ui/unsupported.rs
rename to 0.1.3/tests/ui/unsupported.rs
diff --git a/tests/ui/unsupported.stderr b/0.1.3/tests/ui/unsupported.stderr
similarity index 100%
rename from tests/ui/unsupported.stderr
rename to 0.1.3/tests/ui/unsupported.stderr
diff --git a/tests/unstable.rs b/0.1.3/tests/unstable.rs
similarity index 100%
rename from tests/unstable.rs
rename to 0.1.3/tests/unstable.rs
diff --git a/0.2.1/.cargo_vcs_info.json b/0.2.1/.cargo_vcs_info.json
new file mode 100644
index 0000000..82b08b7
--- /dev/null
+++ b/0.2.1/.cargo_vcs_info.json
@@ -0,0 +1,5 @@
+{
+ "git": {
+ "sha1": "86791e6208128f29ac37a7a51a5657ab125efe59"
+ }
+}
diff --git a/.gitignore b/0.2.1/.gitignore
similarity index 100%
copy from .gitignore
copy to 0.2.1/.gitignore
diff --git a/0.2.1/.travis.yml b/0.2.1/.travis.yml
new file mode 100644
index 0000000..f2d73f8
--- /dev/null
+++ b/0.2.1/.travis.yml
@@ -0,0 +1,16 @@
+language: rust
+
+script:
+ - cargo test
+
+matrix:
+ include:
+ - rust: nightly
+ - rust: beta
+ env: RUSTFLAGS='--cfg remain_stable_testing'
+ - rust: stable
+ env: RUSTFLAGS='--cfg remain_stable_testing'
+ - rust: 1.36.0
+ env: RUSTFLAGS='--cfg remain_stable_testing'
+ - rust: 1.31.0
+ script: cargo check
diff --git a/Android.bp b/0.2.1/Android.bp
similarity index 100%
rename from Android.bp
rename to 0.2.1/Android.bp
diff --git a/0.2.1/Cargo.toml b/0.2.1/Cargo.toml
new file mode 100644
index 0000000..46d9a8f
--- /dev/null
+++ b/0.2.1/Cargo.toml
@@ -0,0 +1,42 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies
+#
+# If you believe there's an error in this file please file an
+# issue against the rust-lang/cargo repository. If you're
+# editing this file be aware that the upstream Cargo.toml
+# will likely look very different (and much more reasonable)
+
+[package]
+edition = "2018"
+name = "remain"
+version = "0.2.1"
+authors = ["David Tolnay <dtolnay@gmail.com>"]
+description = "Compile-time checks that an enum, struct, or match is written in sorted order."
+documentation = "https://docs.rs/remain"
+readme = "README.md"
+license = "MIT OR Apache-2.0"
+repository = "https://github.com/dtolnay/remain"
+
+[lib]
+proc-macro = true
+[dependencies.proc-macro2]
+version = "1.0"
+
+[dependencies.quote]
+version = "1.0"
+
+[dependencies.syn]
+version = "1.0"
+features = ["full", "visit-mut"]
+[dev-dependencies.rustversion]
+version = "1.0"
+
+[dev-dependencies.trybuild]
+version = "1.0.19"
+features = ["diff"]
+[badges.travis-ci]
+repository = "dtolnay/remain"
diff --git a/0.2.1/Cargo.toml.orig b/0.2.1/Cargo.toml.orig
new file mode 100644
index 0000000..8b16b07
--- /dev/null
+++ b/0.2.1/Cargo.toml.orig
@@ -0,0 +1,25 @@
+[package]
+name = "remain"
+version = "0.2.1" # remember to update number in readme for major versions
+authors = ["David Tolnay <dtolnay@gmail.com>"]
+edition = "2018"
+license = "MIT OR Apache-2.0"
+description = "Compile-time checks that an enum, struct, or match is written in sorted order."
+repository = "https://github.com/dtolnay/remain"
+documentation = "https://docs.rs/remain"
+readme = "README.md"
+
+[lib]
+proc-macro = true
+
+[badges]
+travis-ci = { repository = "dtolnay/remain" }
+
+[dependencies]
+proc-macro2 = "1.0"
+quote = "1.0"
+syn = { version = "1.0", features = ["full", "visit-mut"] }
+
+[dev-dependencies]
+rustversion = "1.0"
+trybuild = { version = "1.0.19", features = ["diff"] }
diff --git a/0.2.1/LICENSE b/0.2.1/LICENSE
new file mode 120000
index 0000000..6b579aa
--- /dev/null
+++ b/0.2.1/LICENSE
@@ -0,0 +1 @@
+LICENSE-APACHE
\ No newline at end of file
diff --git a/LICENSE-APACHE b/0.2.1/LICENSE-APACHE
similarity index 100%
copy from LICENSE-APACHE
copy to 0.2.1/LICENSE-APACHE
diff --git a/LICENSE-MIT b/0.2.1/LICENSE-MIT
similarity index 100%
copy from LICENSE-MIT
copy to 0.2.1/LICENSE-MIT
diff --git a/0.2.1/METADATA b/0.2.1/METADATA
new file mode 100644
index 0000000..696b4da
--- /dev/null
+++ b/0.2.1/METADATA
@@ -0,0 +1,18 @@
+name: "remain"
+description:
+ "This crate provides an attribute macro to check at compile time that the "
+ "variants of an enum or the arms of a match expression are written in "
+ "sorted order."
+
+third_party {
+ url {
+ type: HOMEPAGE
+ value: "https://crates.io/crates/remain"
+ }
+ url {
+ type: GIT
+ value: "https://github.com/dtolnay/remain"
+ }
+ version: "0.2.1"
+ last_upgrade_date { year: 2020 month: 3 day: 5 }
+}
diff --git a/0.2.1/MODULE_LICENSE_APACHE2 b/0.2.1/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/0.2.1/MODULE_LICENSE_APACHE2
diff --git a/0.2.1/NOTICE b/0.2.1/NOTICE
new file mode 120000
index 0000000..7a694c9
--- /dev/null
+++ b/0.2.1/NOTICE
@@ -0,0 +1 @@
+LICENSE
\ No newline at end of file
diff --git a/0.2.1/README.md b/0.2.1/README.md
new file mode 100644
index 0000000..3e3d553
--- /dev/null
+++ b/0.2.1/README.md
@@ -0,0 +1,135 @@
+Remain sorted
+=============
+
+[![Build Status](https://api.travis-ci.com/dtolnay/remain.svg?branch=master)](https://travis-ci.com/dtolnay/remain)
+[![Latest Version](https://img.shields.io/crates/v/remain.svg)](https://crates.io/crates/remain)
+[![Rust Documentation](https://img.shields.io/badge/api-rustdoc-blue.svg)](https://docs.rs/remain)
+
+This crate provides an attribute macro to check at compile time that the
+variants of an enum or the arms of a match expression are written in sorted
+order.
+
+```toml
+[dependencies]
+remain = "0.2"
+```
+
+## Syntax
+
+Place a `#[remain::sorted]` attribute on enums, structs, match-expressions, or
+let-statements whose value is a match-expression.
+
+Alternatively, import as `use remain::sorted;` and use `#[sorted]` as the
+attribute.
+
+```rust
+#[remain::sorted]
+#[derive(Debug)]
+pub enum Error {
+ BlockSignal(signal::Error),
+ CreateCrasClient(libcras::Error),
+ CreateEventFd(sys_util::Error),
+ CreateSignalFd(sys_util::SignalFdError),
+ CreateSocket(io::Error),
+ DetectImageType(qcow::Error),
+ DeviceJail(io_jail::Error),
+ NetDeviceNew(virtio::NetError),
+ SpawnVcpu(io::Error),
+}
+
+#[remain::sorted]
+#[derive(Debug)]
+pub struct Registers {
+ ax: u16,
+ cx: u16,
+ di: u16,
+ si: u16,
+ sp: u16,
+}
+
+impl Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use self::Error::*;
+
+ #[remain::sorted]
+ match self {
+ BlockSignal(e) => write!(f, "failed to block signal: {}", e),
+ CreateCrasClient(e) => write!(f, "failed to create cras client: {}", e),
+ CreateEventFd(e) => write!(f, "failed to create eventfd: {}", e),
+ CreateSignalFd(e) => write!(f, "failed to create signalfd: {}", e),
+ CreateSocket(e) => write!(f, "failed to create socket: {}", e),
+ DetectImageType(e) => write!(f, "failed to detect disk image type: {}", e),
+ DeviceJail(e) => write!(f, "failed to jail device: {}", e),
+ NetDeviceNew(e) => write!(f, "failed to set up virtio networking: {}", e),
+ SpawnVcpu(e) => write!(f, "failed to spawn VCPU thread: {}", e),
+ }
+ }
+}
+```
+
+If an enum variant, struct field, or match arm is inserted out of order,
+
+```diff
+ NetDeviceNew(virtio::NetError),
+ SpawnVcpu(io::Error),
++ AaaUhOh(Box<dyn StdError>),
+ }
+```
+
+then the macro produces a compile error.
+
+```console
+error: AaaUhOh should sort before BlockSignal
+ --> tests/stable.rs:49:5
+ |
+49 | AaaUhOh(Box<dyn StdError>),
+ | ^^^^^^^
+```
+
+## Compiler support
+
+The attribute on enums and structs is supported on any rustc version 1.31+.
+
+Rust does not yet have stable support for user-defined attributes within a
+function body, so the attribute on match-expressions and let-statements requires
+a nightly compiler and the following two features enabled:
+
+```rust
+#![feature(proc_macro_hygiene, stmt_expr_attributes)]
+```
+
+As a stable alternative, this crate provides a function-level attribute called
+`#[remain::check]` which makes match-expression and let-statement attributes
+work on any rustc version 1.31+. Place this attribute on any function containing
+`#[sorted]` to make them work on a stable compiler.
+
+```rust
+impl Display for Error {
+ #[remain::check]
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use self::Error::*;
+
+ #[sorted]
+ match self {
+ /* ... */
+ }
+ }
+}
+```
+
+<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/0.2.1/src/atom.rs b/0.2.1/src/atom.rs
new file mode 100644
index 0000000..bd1ef86
--- /dev/null
+++ b/0.2.1/src/atom.rs
@@ -0,0 +1,141 @@
+use std::cmp::{Ord, Ordering, PartialOrd};
+use std::str;
+
+#[derive(Copy, Clone, Eq, PartialEq)]
+pub enum Atom<'a> {
+ /// A sequence of underscores.
+ Underscore(usize),
+ /// A sequence of digits.
+ Number(&'a str),
+ /// A sequence of characters.
+ Chars(&'a str),
+}
+
+impl Atom<'_> {
+ pub fn underscores(&self) -> usize {
+ match *self {
+ Atom::Underscore(n) => n,
+ _ => 0,
+ }
+ }
+}
+
+impl PartialOrd for Atom<'_> {
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ Some(self.cmp(other))
+ }
+}
+
+impl Ord for Atom<'_> {
+ fn cmp(&self, other: &Self) -> Ordering {
+ use self::Atom::*;
+
+ match (self, other) {
+ (Underscore(l), Underscore(r)) => l.cmp(r),
+ (Underscore(_), _) => Ordering::Less,
+ (_, Underscore(_)) => Ordering::Greater,
+ (Number(l), Number(r)) => cmp_numeric(l, r),
+ (Number(_), Chars(_)) => Ordering::Less,
+ (Chars(_), Number(_)) => Ordering::Greater,
+ (Chars(l), Chars(r)) => cmp_ignore_case(l, r),
+ }
+ }
+}
+
+fn cmp_numeric(l: &str, r: &str) -> Ordering {
+ // Trim leading zeros.
+ let l = l.trim_start_matches('0');
+ let r = r.trim_start_matches('0');
+
+ match l.len().cmp(&r.len()) {
+ Ordering::Equal => l.cmp(r),
+ non_eq => non_eq,
+ }
+}
+
+fn cmp_ignore_case(l: &str, r: &str) -> Ordering {
+ for (a, b) in l.bytes().zip(r.bytes()) {
+ match a.to_ascii_lowercase().cmp(&b.to_ascii_lowercase()) {
+ Ordering::Equal => match a.cmp(&b) {
+ Ordering::Equal => {}
+ non_eq => return non_eq,
+ },
+ non_eq => return non_eq,
+ }
+ }
+
+ l.len().cmp(&r.len())
+}
+
+pub fn iter_atoms(string: &str) -> AtomIter {
+ AtomIter {
+ bytes: string.as_bytes(),
+ offset: 0,
+ }
+}
+
+pub struct AtomIter<'a> {
+ bytes: &'a [u8],
+ offset: usize,
+}
+
+impl<'a> Iterator for AtomIter<'a> {
+ type Item = Atom<'a>;
+
+ fn next(&mut self) -> Option<Atom<'a>> {
+ if self.offset >= self.bytes.len() {
+ return None;
+ }
+
+ let x = self.bytes[self.offset];
+
+ match x {
+ b'_' => {
+ self.offset += 1;
+
+ let mut n = 1;
+ while self.offset < self.bytes.len() {
+ match self.bytes[self.offset] {
+ b'_' => {
+ self.offset += 1;
+ n += 1;
+ }
+ _ => break,
+ }
+ }
+
+ Some(Atom::Underscore(n))
+ }
+ b'0'..=b'9' => {
+ let start = self.offset;
+
+ self.offset += 1;
+ while self.offset < self.bytes.len() {
+ match self.bytes[self.offset] {
+ b'0'..=b'9' => self.offset += 1,
+ _ => break,
+ }
+ }
+
+ let bytes = &self.bytes[start..self.offset];
+ let number = str::from_utf8(bytes).expect("valid utf8");
+ Some(Atom::Number(number))
+ }
+ _ => {
+ let start = self.offset;
+
+ self.offset += 1;
+ while self.offset < self.bytes.len() {
+ match self.bytes[self.offset] {
+ b'_' | b'0'..=b'9' => break,
+ _ => self.offset += 1,
+ }
+ }
+
+ let bytes = &self.bytes[start..self.offset];
+ let chars = str::from_utf8(bytes).expect("valid utf8");
+ Some(Atom::Chars(chars))
+ }
+ }
+ }
+}
diff --git a/0.2.1/src/check.rs b/0.2.1/src/check.rs
new file mode 100644
index 0000000..0643f5f
--- /dev/null
+++ b/0.2.1/src/check.rs
@@ -0,0 +1,136 @@
+use quote::quote;
+use std::cmp::Ordering;
+use syn::{Arm, Attribute, Ident, Result, Variant};
+use syn::{Error, Field, Pat, PatIdent};
+
+use crate::compare::{cmp, Path, UnderscoreOrder};
+use crate::format;
+use crate::parse::Input::{self, *};
+
+pub fn sorted(input: &mut Input) -> Result<()> {
+ let paths = match input {
+ Enum(item) => collect_paths(&mut item.variants)?,
+ Struct(item) => collect_paths(&mut item.fields)?,
+ Match(expr) | Let(expr) => collect_paths(&mut expr.arms)?,
+ };
+
+ let mode = UnderscoreOrder::First;
+ if find_misordered(&paths, mode).is_none() {
+ return Ok(());
+ }
+
+ let mode = UnderscoreOrder::Last;
+ let wrong = match find_misordered(&paths, mode) {
+ Some(wrong) => wrong,
+ None => return Ok(()),
+ };
+
+ let lesser = &paths[wrong];
+ let correct_pos = match paths[..wrong - 1].binary_search_by(|probe| cmp(probe, lesser, mode)) {
+ Err(correct_pos) => correct_pos,
+ Ok(equal_to) => equal_to + 1,
+ };
+ let greater = &paths[correct_pos];
+ Err(format::error(lesser, greater))
+}
+
+fn find_misordered(paths: &[Path], mode: UnderscoreOrder) -> Option<usize> {
+ for i in 1..paths.len() {
+ if cmp(&paths[i], &paths[i - 1], mode) == Ordering::Less {
+ return Some(i);
+ }
+ }
+
+ None
+}
+
+fn collect_paths<'a, I, P>(iter: I) -> Result<Vec<Path>>
+where
+ I: IntoIterator<Item = &'a mut P>,
+ P: Sortable + 'a,
+{
+ iter.into_iter()
+ .filter_map(|item| {
+ if remove_unsorted_attr(item.attrs()) {
+ None
+ } else {
+ Some(item.to_path())
+ }
+ })
+ .collect()
+}
+
+fn remove_unsorted_attr(attrs: &mut Vec<Attribute>) -> bool {
+ for i in 0..attrs.len() {
+ let path = &attrs[i].path;
+ let path = quote!(#path).to_string();
+ if path == "unsorted" || path == "remain :: unsorted" {
+ attrs.remove(i);
+ return true;
+ }
+ }
+
+ false
+}
+
+trait Sortable {
+ fn to_path(&self) -> Result<Path>;
+ fn attrs(&mut self) -> &mut Vec<Attribute>;
+}
+
+impl Sortable for Variant {
+ fn to_path(&self) -> Result<Path> {
+ Ok(Path {
+ segments: vec![self.ident.clone()],
+ })
+ }
+ fn attrs(&mut self) -> &mut Vec<Attribute> {
+ &mut self.attrs
+ }
+}
+
+impl Sortable for Field {
+ fn to_path(&self) -> Result<Path> {
+ Ok(Path {
+ segments: vec![self.ident.clone().expect("must be named field")],
+ })
+ }
+ fn attrs(&mut self) -> &mut Vec<Attribute> {
+ &mut self.attrs
+ }
+}
+
+impl Sortable for Arm {
+ fn to_path(&self) -> Result<Path> {
+ // Sort by just the first pat.
+ let pat = match &self.pat {
+ Pat::Or(pat) => pat.cases.iter().next().expect("at least one pat"),
+ _ => &self.pat,
+ };
+
+ let segments = match pat {
+ Pat::Ident(pat) if is_just_ident(&pat) => vec![pat.ident.clone()],
+ Pat::Path(pat) => idents_of_path(&pat.path),
+ Pat::Struct(pat) => idents_of_path(&pat.path),
+ Pat::TupleStruct(pat) => idents_of_path(&pat.path),
+ Pat::Wild(pat) => vec![Ident::from(pat.underscore_token)],
+ other => {
+ let msg = "unsupported by #[remain::sorted]";
+ return Err(Error::new_spanned(other, msg));
+ }
+ };
+
+ Ok(Path { segments })
+ }
+ fn attrs(&mut self) -> &mut Vec<Attribute> {
+ &mut self.attrs
+ }
+}
+
+fn idents_of_path(path: &syn::Path) -> Vec<Ident> {
+ path.segments.iter().map(|seg| seg.ident.clone()).collect()
+}
+
+fn is_just_ident(pat: &PatIdent) -> bool {
+ pat.by_ref.is_none() && pat.mutability.is_none() && pat.subpat.is_none()
+}
diff --git a/0.2.1/src/compare.rs b/0.2.1/src/compare.rs
new file mode 100644
index 0000000..3fa4198
--- /dev/null
+++ b/0.2.1/src/compare.rs
@@ -0,0 +1,68 @@
+use proc_macro2::Ident;
+use std::cmp::Ordering;
+
+use crate::atom::iter_atoms;
+
+#[derive(Copy, Clone, PartialEq)]
+pub enum UnderscoreOrder {
+ First,
+ Last,
+}
+
+pub struct Path {
+ pub segments: Vec<Ident>,
+}
+
+pub fn cmp(lhs: &Path, rhs: &Path, mode: UnderscoreOrder) -> Ordering {
+ // Lexicographic ordering across path segments.
+ for (lhs, rhs) in lhs.segments.iter().zip(&rhs.segments) {
+ match cmp_segment(&lhs.to_string(), &rhs.to_string(), mode) {
+ Ordering::Equal => {}
+ non_eq => return non_eq,
+ }
+ }
+
+ lhs.segments.len().cmp(&rhs.segments.len())
+}
+
+fn cmp_segment(lhs: &str, rhs: &str, mode: UnderscoreOrder) -> Ordering {
+ // Sort `_` last.
+ match (lhs, rhs) {
+ ("_", "_") => return Ordering::Equal,
+ ("_", _) => return Ordering::Greater,
+ (_, "_") => return Ordering::Less,
+ (_, _) => {}
+ }
+
+ let mut lhs_atoms = iter_atoms(lhs);
+ let mut rhs_atoms = iter_atoms(rhs);
+
+ // Path segments can't be empty.
+ let mut left = lhs_atoms.next().unwrap();
+ let mut right = rhs_atoms.next().unwrap();
+
+ if mode == UnderscoreOrder::Last {
+ // Compare leading underscores.
+ match left.underscores().cmp(&right.underscores()) {
+ Ordering::Equal => {}
+ non_eq => return non_eq,
+ }
+ }
+
+ loop {
+ match left.cmp(&right) {
+ Ordering::Equal => {}
+ non_eq => return non_eq,
+ }
+
+ match (lhs_atoms.next(), rhs_atoms.next()) {
+ (None, None) => return Ordering::Equal,
+ (None, Some(_)) => return Ordering::Less,
+ (Some(_), None) => return Ordering::Greater,
+ (Some(nextl), Some(nextr)) => {
+ left = nextl;
+ right = nextr;
+ }
+ }
+ }
+}
diff --git a/0.2.1/src/emit.rs b/0.2.1/src/emit.rs
new file mode 100644
index 0000000..4a051ba
--- /dev/null
+++ b/0.2.1/src/emit.rs
@@ -0,0 +1,39 @@
+use proc_macro::TokenStream;
+use proc_macro2::Span;
+use quote::quote;
+use syn::Error;
+
+#[derive(Copy, Clone)]
+pub enum Kind {
+ Enum,
+ Match,
+ Struct,
+ Let,
+}
+
+pub fn emit(err: Error, kind: Kind, output: TokenStream) -> TokenStream {
+ let mut err = err;
+ if !probably_has_spans(kind) {
+ // Otherwise the error is printed without any line number.
+ err = Error::new(Span::call_site(), &err.to_string());
+ }
+
+ let err = err.to_compile_error();
+ let output = proc_macro2::TokenStream::from(output);
+
+ let expanded = match kind {
+ Kind::Enum | Kind::Let | Kind::Struct => quote!(#err #output),
+ Kind::Match => quote!({ #err #output }),
+ };
+
+ TokenStream::from(expanded)
+}
+
+// Rustc is so bad at spans.
+// https://github.com/rust-lang/rust/issues/43081
+fn probably_has_spans(kind: Kind) -> bool {
+ match kind {
+ Kind::Enum | Kind::Struct => true,
+ Kind::Match | Kind::Let => false,
+ }
+}
diff --git a/src/format.rs b/0.2.1/src/format.rs
similarity index 100%
copy from src/format.rs
copy to 0.2.1/src/format.rs
diff --git a/0.2.1/src/lib.rs b/0.2.1/src/lib.rs
new file mode 100644
index 0000000..5648e28
--- /dev/null
+++ b/0.2.1/src/lib.rs
@@ -0,0 +1,180 @@
+//! This crate provides an attribute macro to check at compile time that the
+//! variants of an enum or the arms of a match expression are written in sorted
+//! order.
+//!
+//! # Syntax
+//!
+//! Place a `#[remain::sorted]` attribute on enums, structs, match-expressions,
+//! or let-statements whose value is a match-expression.
+//!
+//! Alternatively, import as `use remain::sorted;` and use `#[sorted]` as the
+//! attribute.
+//!
+//! ```
+//! # use std::error::Error as StdError;
+//! # use std::fmt::{self, Display};
+//! # use std::io;
+//! #
+//! #[remain::sorted]
+//! #[derive(Debug)]
+//! pub enum Error {
+//! BlockSignal(signal::Error),
+//! CreateCrasClient(libcras::Error),
+//! CreateEventFd(sys_util::Error),
+//! CreateSignalFd(sys_util::SignalFdError),
+//! CreateSocket(io::Error),
+//! DetectImageType(qcow::Error),
+//! DeviceJail(io_jail::Error),
+//! NetDeviceNew(virtio::NetError),
+//! SpawnVcpu(io::Error),
+//! }
+//!
+//! impl Display for Error {
+//! # #[remain::check]
+//! fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+//! use self::Error::*;
+//!
+//! #[remain::sorted]
+//! match self {
+//! BlockSignal(e) => write!(f, "failed to block signal: {}", e),
+//! CreateCrasClient(e) => write!(f, "failed to create cras client: {}", e),
+//! CreateEventFd(e) => write!(f, "failed to create eventfd: {}", e),
+//! CreateSignalFd(e) => write!(f, "failed to create signalfd: {}", e),
+//! CreateSocket(e) => write!(f, "failed to create socket: {}", e),
+//! DetectImageType(e) => write!(f, "failed to detect disk image type: {}", e),
+//! DeviceJail(e) => write!(f, "failed to jail device: {}", e),
+//! NetDeviceNew(e) => write!(f, "failed to set up virtio networking: {}", e),
+//! SpawnVcpu(e) => write!(f, "failed to spawn VCPU thread: {}", e),
+//! }
+//! }
+//! }
+//! #
+//! # mod signal {
+//! # pub use std::io::Error;
+//! # }
+//! #
+//! # mod libcras {
+//! # pub use std::io::Error;
+//! # }
+//! #
+//! # mod sys_util {
+//! # pub use std::io::{Error, Error as SignalFdError};
+//! # }
+//! #
+//! # mod qcow {
+//! # pub use std::io::Error;
+//! # }
+//! #
+//! # mod io_jail {
+//! # pub use std::io::Error;
+//! # }
+//! #
+//! # mod virtio {
+//! # pub use std::io::Error as NetError;
+//! # }
+//! #
+//! # fn main() {}
+//! ```
+//!
+//! If an enum variant, struct field, or match arm is inserted out of order,\
+//!
+//! ```diff
+//! NetDeviceNew(virtio::NetError),
+//! SpawnVcpu(io::Error),
+//! + AaaUhOh(Box<dyn StdError>),
+//! }
+//! ```
+//!
+//! then the macro produces a compile error.
+//!
+//! ```console
+//! error: AaaUhOh should sort before BlockSignal
+//! --> tests/stable.rs:49:5
+//! |
+//! 49 | AaaUhOh(Box<dyn StdError>),
+//! | ^^^^^^^
+//! ```
+//!
+//! # Compiler support
+//!
+//! The attribute on enums is supported on any rustc version 1.31+.
+//!
+//! Rust does not yet have stable support for user-defined attributes within a
+//! function body, so the attribute on match-expressions and let-statements
+//! requires a nightly compiler and the following two features enabled:
+//!
+//! ```
+//! # const IGNORE: &str = stringify! {
+//! #![feature(proc_macro_hygiene, stmt_expr_attributes)]
+//! # };
+//! ```
+//!
+//! As a stable alternative, this crate provides a function-level attribute
+//! called `#[remain::check]` which makes match-expression and let-statement
+//! attributes work on any rustc version 1.31+. Place this attribute on any
+//! function containing `#[sorted]` to make them work on a stable compiler.
+//!
+//! ```
+//! # use std::fmt::{self, Display};
+//! #
+//! # enum Error {}
+//! #
+//! impl Display for Error {
+//! #[remain::check]
+//! fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+//! use self::Error::*;
+//!
+//! #[sorted]
+//! match self {
+//! /* ... */
+//! # _ => unimplemented!(),
+//! }
+//! }
+//! }
+//! #
+//! # fn main() {}
+//! ```
+
+#![allow(clippy::needless_doctest_main)]
+
+extern crate proc_macro;
+
+mod atom;
+mod check;
+mod compare;
+mod emit;
+mod format;
+mod parse;
+mod visit;
+
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::{parse_macro_input, ItemFn};
+
+use crate::emit::emit;
+use crate::parse::{Input, Nothing};
+
+#[proc_macro_attribute]
+pub fn sorted(args: TokenStream, input: TokenStream) -> TokenStream {
+ let _ = parse_macro_input!(args as Nothing);
+ let mut input = parse_macro_input!(input as Input);
+ let kind = input.kind();
+
+ let result = check::sorted(&mut input);
+ let output = TokenStream::from(quote!(#input));
+
+ match result {
+ Ok(_) => output,
+ Err(err) => emit(err, kind, output),
+ }
+}
+
+#[proc_macro_attribute]
+pub fn check(args: TokenStream, input: TokenStream) -> TokenStream {
+ let _ = parse_macro_input!(args as Nothing);
+ let mut input = parse_macro_input!(input as ItemFn);
+
+ visit::check(&mut input);
+
+ TokenStream::from(quote!(#input))
+}
diff --git a/0.2.1/src/parse.rs b/0.2.1/src/parse.rs
new file mode 100644
index 0000000..aff4972
--- /dev/null
+++ b/0.2.1/src/parse.rs
@@ -0,0 +1,91 @@
+use proc_macro2::{Span, TokenStream};
+use quote::ToTokens;
+use syn::parse::{Parse, ParseStream};
+use syn::{Attribute, Error, Expr, Fields, Result, Stmt, Token, Visibility};
+
+use crate::emit::Kind;
+
+pub struct Nothing;
+
+impl Parse for Nothing {
+ fn parse(_input: ParseStream) -> Result<Self> {
+ Ok(Nothing)
+ }
+}
+
+pub enum Input {
+ Enum(syn::ItemEnum),
+ Match(syn::ExprMatch),
+ Struct(syn::ItemStruct),
+ Let(syn::ExprMatch),
+}
+
+impl Input {
+ pub fn kind(&self) -> Kind {
+ match self {
+ Input::Enum(_) => Kind::Enum,
+ Input::Match(_) => Kind::Match,
+ Input::Struct(_) => Kind::Struct,
+ Input::Let(_) => Kind::Let,
+ }
+ }
+}
+
+impl Parse for Input {
+ fn parse(input: ParseStream) -> Result<Self> {
+ let ahead = input.fork();
+ let _ = ahead.call(Attribute::parse_outer)?;
+
+ if ahead.peek(Token![match]) {
+ let expr = match input.parse()? {
+ Expr::Match(expr) => expr,
+ _ => unreachable!("expected match"),
+ };
+ return Ok(Input::Match(expr));
+ }
+
+ if ahead.peek(Token![let]) {
+ let stmt = match input.parse()? {
+ Stmt::Local(stmt) => stmt,
+ _ => unreachable!("expected let"),
+ };
+ let init = match stmt.init {
+ Some((_, init)) => *init,
+ None => return Err(unexpected()),
+ };
+ let expr = match init {
+ Expr::Match(expr) => expr,
+ _ => return Err(unexpected()),
+ };
+ return Ok(Input::Let(expr));
+ }
+
+ let _: Visibility = ahead.parse()?;
+ if ahead.peek(Token![enum]) {
+ return input.parse().map(Input::Enum);
+ } else if ahead.peek(Token![struct]) {
+ let input: syn::ItemStruct = input.parse()?;
+ if let Fields::Named(_) = &input.fields {
+ return Ok(Input::Struct(input));
+ }
+ }
+
+ Err(unexpected())
+ }
+}
+
+impl ToTokens for Input {
+ fn to_tokens(&self, tokens: &mut TokenStream) {
+ match self {
+ Input::Enum(item) => item.to_tokens(tokens),
+ Input::Struct(item) => item.to_tokens(tokens),
+ Input::Match(expr) | Input::Let(expr) => expr.to_tokens(tokens),
+ }
+ }
+}
+
+fn unexpected() -> Error {
+ let span = Span::call_site();
+ let msg = "expected enum, struct, or match expression";
+ Error::new(span, msg)
+}
diff --git a/0.2.1/src/visit.rs b/0.2.1/src/visit.rs
new file mode 100644
index 0000000..f5cecbd
--- /dev/null
+++ b/0.2.1/src/visit.rs
@@ -0,0 +1,79 @@
+use quote::quote;
+use syn::visit_mut::{self, VisitMut};
+use syn::{parse_quote, Attribute, Expr, ExprMatch, ItemFn, Local};
+
+use crate::parse::Input;
+
+pub fn check(input: &mut ItemFn) {
+ Checker.visit_item_fn_mut(input);
+}
+
+struct Checker;
+
+impl VisitMut for Checker {
+ fn visit_expr_mut(&mut self, expr: &mut Expr) {
+ visit_mut::visit_expr_mut(self, expr);
+
+ let expr_match = match expr {
+ Expr::Match(expr) => expr,
+ _ => return,
+ };
+
+ if !take_sorted_attr(&mut expr_match.attrs) {
+ return;
+ }
+
+ let input = expr_match.clone();
+ check_and_insert_error(input, expr);
+ }
+
+ fn visit_local_mut(&mut self, local: &mut Local) {
+ visit_mut::visit_local_mut(self, local);
+
+ let init = match &local.init {
+ Some((_, init)) => init,
+ None => return,
+ };
+
+ let expr_match = match init.as_ref() {
+ Expr::Match(expr) => expr,
+ _ => return,
+ };
+
+ if !take_sorted_attr(&mut local.attrs) {
+ return;
+ }
+
+ let input = expr_match.clone();
+ let expr = local.init.as_mut().unwrap().1.as_mut();
+ check_and_insert_error(input, expr);
+ }
+}
+
+fn take_sorted_attr(attrs: &mut Vec<Attribute>) -> bool {
+ for i in 0..attrs.len() {
+ let path = &attrs[i].path;
+ let path = quote!(#path).to_string();
+ if path == "sorted" || path == "remain :: sorted" {
+ attrs.remove(i);
+ return true;
+ }
+ }
+
+ false
+}
+
+fn check_and_insert_error(input: ExprMatch, out: &mut Expr) {
+ let mut input = Input::Match(input);
+
+ *out = match crate::check::sorted(&mut input) {
+ Ok(_) => parse_quote!(#input),
+ Err(err) => {
+ let err = err.to_compile_error();
+ parse_quote!({
+ #err
+ #input
+ })
+ }
+ };
+}
diff --git a/0.2.1/tests/compiletest.rs b/0.2.1/tests/compiletest.rs
new file mode 100644
index 0000000..f9aea23
--- /dev/null
+++ b/0.2.1/tests/compiletest.rs
@@ -0,0 +1,6 @@
+#[rustversion::attr(not(nightly), ignore)]
+#[test]
+fn ui() {
+ let t = trybuild::TestCases::new();
+ t.compile_fail("tests/ui/*.rs");
+}
diff --git a/0.2.1/tests/order.rs b/0.2.1/tests/order.rs
new file mode 100644
index 0000000..4723f94
--- /dev/null
+++ b/0.2.1/tests/order.rs
@@ -0,0 +1,57 @@
+#![allow(dead_code, non_camel_case_types)]
+
+#[remain::sorted]
+enum UnderscoresFirst {
+ __Nonexhaustive,
+ Aaa,
+ Bbb,
+}
+
+#[remain::sorted]
+enum UnderscoresLast {
+ Aaa,
+ Bbb,
+ __Nonexhaustive,
+}
+
+#[remain::sorted]
+enum SnakeCase {
+ under_score,
+ underscore,
+}
+
+#[remain::sorted]
+enum NumberingSimple {
+ E1,
+ E9,
+ E10,
+}
+
+#[remain::sorted]
+enum NumberingComplex {
+ E1_Aaa,
+ E9_Aaa,
+ E10_Aaa,
+}
+
+#[remain::sorted]
+enum AtomOrder {
+ A,
+ A_,
+ A0,
+ AA,
+ Aa,
+ under_0core,
+ under_Score,
+ under_score,
+ under__0core,
+ under__Score,
+ under__score,
+ underscore,
+}
+
+#[remain::sorted]
+enum LargeNumber {
+ E1,
+ E99999999999999999999999,
+}
diff --git a/0.2.1/tests/stable.rs b/0.2.1/tests/stable.rs
new file mode 100644
index 0000000..b1bcd65
--- /dev/null
+++ b/0.2.1/tests/stable.rs
@@ -0,0 +1,74 @@
+#![allow(dead_code)]
+
+#[remain::sorted]
+#[derive(PartialEq)]
+pub enum TestEnum {
+ A,
+ B,
+ #[remain::unsorted]
+ Ignored,
+ C,
+ #[unsorted]
+ AlsoIgnored,
+ D,
+ __Nonexhaustive,
+}
+
+#[remain::sorted]
+#[derive(PartialEq)]
+pub struct TestStruct {
+ a: usize,
+ b: usize,
+ #[unsorted]
+ ignored: usize,
+ c: usize,
+ #[remain::unsorted]
+ also_ignored: usize,
+ d: usize,
+}
+
+#[test]
+fn test_attrs() {
+ fn is_partial_eq<T: PartialEq>() -> bool {
+ true
+ }
+
+ assert!(is_partial_eq::<TestEnum>());
+ assert!(is_partial_eq::<TestStruct>());
+}
+
+#[test]
+#[remain::check]
+fn test_let() {
+ let value = TestEnum::A;
+
+ #[sorted]
+ let _ = match value {
+ TestEnum::A => {}
+ #[remain::unsorted]
+ TestEnum::Ignored => {}
+ TestEnum::B => {}
+ #[unsorted]
+ TestEnum::AlsoIgnored => {}
+ TestEnum::C => {}
+ _ => {}
+ };
+}
+
+#[test]
+#[remain::check]
+fn test_match() {
+ let value = TestEnum::A;
+
+ #[sorted]
+ match value {
+ TestEnum::A => {}
+ TestEnum::B => {}
+ #[unsorted]
+ TestEnum::Ignored => {}
+ TestEnum::C => {}
+ #[remain::unsorted]
+ TestEnum::AlsoIgnored => {}
+ _ => {}
+ }
+}
diff --git a/tests/ui/enum.rs b/0.2.1/tests/ui/enum.rs
similarity index 100%
copy from tests/ui/enum.rs
copy to 0.2.1/tests/ui/enum.rs
diff --git a/tests/ui/enum.stderr b/0.2.1/tests/ui/enum.stderr
similarity index 100%
copy from tests/ui/enum.stderr
copy to 0.2.1/tests/ui/enum.stderr
diff --git a/tests/ui/let-stable.rs b/0.2.1/tests/ui/let-stable.rs
similarity index 100%
copy from tests/ui/let-stable.rs
copy to 0.2.1/tests/ui/let-stable.rs
diff --git a/tests/ui/let-stable.stderr b/0.2.1/tests/ui/let-stable.stderr
similarity index 100%
copy from tests/ui/let-stable.stderr
copy to 0.2.1/tests/ui/let-stable.stderr
diff --git a/tests/ui/let-unstable.rs b/0.2.1/tests/ui/let-unstable.rs
similarity index 100%
copy from tests/ui/let-unstable.rs
copy to 0.2.1/tests/ui/let-unstable.rs
diff --git a/tests/ui/let-unstable.stderr b/0.2.1/tests/ui/let-unstable.stderr
similarity index 100%
copy from tests/ui/let-unstable.stderr
copy to 0.2.1/tests/ui/let-unstable.stderr
diff --git a/tests/ui/match-stable.rs b/0.2.1/tests/ui/match-stable.rs
similarity index 100%
copy from tests/ui/match-stable.rs
copy to 0.2.1/tests/ui/match-stable.rs
diff --git a/tests/ui/match-stable.stderr b/0.2.1/tests/ui/match-stable.stderr
similarity index 100%
copy from tests/ui/match-stable.stderr
copy to 0.2.1/tests/ui/match-stable.stderr
diff --git a/tests/ui/match-unstable.rs b/0.2.1/tests/ui/match-unstable.rs
similarity index 100%
copy from tests/ui/match-unstable.rs
copy to 0.2.1/tests/ui/match-unstable.rs
diff --git a/tests/ui/match-unstable.stderr b/0.2.1/tests/ui/match-unstable.stderr
similarity index 100%
copy from tests/ui/match-unstable.stderr
copy to 0.2.1/tests/ui/match-unstable.stderr
diff --git a/0.2.1/tests/ui/repeat.rs b/0.2.1/tests/ui/repeat.rs
new file mode 100644
index 0000000..a0fe0ec
--- /dev/null
+++ b/0.2.1/tests/ui/repeat.rs
@@ -0,0 +1,14 @@
+enum E {
+ Aaa(u8),
+ Bbb,
+}
+
+#[remain::check]
+fn main() {
+ #[sorted]
+ match E::Bbb {
+ E::Aaa(0) => {}
+ E::Bbb => {}
+ E::Aaa(_) => {}
+ }
+}
diff --git a/0.2.1/tests/ui/repeat.stderr b/0.2.1/tests/ui/repeat.stderr
new file mode 100644
index 0000000..4dbf277
--- /dev/null
+++ b/0.2.1/tests/ui/repeat.stderr
@@ -0,0 +1,5 @@
+error: E::Aaa should sort before E::Bbb
+ --> $DIR/repeat.rs:12:9
+ |
+12 | E::Aaa(_) => {}
+ | ^^^^^^
diff --git a/tests/ui/struct.rs b/0.2.1/tests/ui/struct.rs
similarity index 100%
copy from tests/ui/struct.rs
copy to 0.2.1/tests/ui/struct.rs
diff --git a/tests/ui/struct.stderr b/0.2.1/tests/ui/struct.stderr
similarity index 100%
copy from tests/ui/struct.stderr
copy to 0.2.1/tests/ui/struct.stderr
diff --git a/tests/ui/unnamed-fields.rs b/0.2.1/tests/ui/unnamed-fields.rs
similarity index 100%
copy from tests/ui/unnamed-fields.rs
copy to 0.2.1/tests/ui/unnamed-fields.rs
diff --git a/tests/ui/unnamed-fields.stderr b/0.2.1/tests/ui/unnamed-fields.stderr
similarity index 100%
copy from tests/ui/unnamed-fields.stderr
copy to 0.2.1/tests/ui/unnamed-fields.stderr
diff --git a/0.2.1/tests/ui/unsorted-enum.rs b/0.2.1/tests/ui/unsorted-enum.rs
new file mode 100644
index 0000000..f4e299b
--- /dev/null
+++ b/0.2.1/tests/ui/unsorted-enum.rs
@@ -0,0 +1,12 @@
+use remain::sorted;
+
+#[sorted]
+enum E {
+ Aaa,
+ Ccc(u8),
+ #[unsorted]
+ Ddd { u: u8 },
+ Bbb(u8, u8),
+}
+
+fn main() {}
diff --git a/0.2.1/tests/ui/unsorted-enum.stderr b/0.2.1/tests/ui/unsorted-enum.stderr
new file mode 100644
index 0000000..249d171
--- /dev/null
+++ b/0.2.1/tests/ui/unsorted-enum.stderr
@@ -0,0 +1,5 @@
+error: Bbb should sort before Ccc
+ --> $DIR/unsorted-enum.rs:9:5
+ |
+9 | Bbb(u8, u8),
+ | ^^^
diff --git a/0.2.1/tests/ui/unsorted-match-stable.rs b/0.2.1/tests/ui/unsorted-match-stable.rs
new file mode 100644
index 0000000..f85202f
--- /dev/null
+++ b/0.2.1/tests/ui/unsorted-match-stable.rs
@@ -0,0 +1,20 @@
+enum E {
+ Aaa,
+ Bbb(u8, u8),
+ Ccc(u8),
+ Ddd { u: u8 },
+}
+
+#[remain::check]
+fn main() {
+ let value = E::Aaa;
+
+ #[sorted]
+ match value {
+ E::Aaa => {}
+ E::Ccc(_) => {}
+ #[unsorted]
+ E::Ddd { u: _ } => {}
+ E::Bbb(_, _) => {}
+ }
+}
diff --git a/0.2.1/tests/ui/unsorted-match-stable.stderr b/0.2.1/tests/ui/unsorted-match-stable.stderr
new file mode 100644
index 0000000..d04d317
--- /dev/null
+++ b/0.2.1/tests/ui/unsorted-match-stable.stderr
@@ -0,0 +1,5 @@
+error: E::Bbb should sort before E::Ccc
+ --> $DIR/unsorted-match-stable.rs:18:9
+ |
+18 | E::Bbb(_, _) => {}
+ | ^^^^^^
diff --git a/0.2.1/tests/ui/unsorted-match-unstable.rs b/0.2.1/tests/ui/unsorted-match-unstable.rs
new file mode 100644
index 0000000..b3e95a6
--- /dev/null
+++ b/0.2.1/tests/ui/unsorted-match-unstable.rs
@@ -0,0 +1,23 @@
+#![feature(proc_macro_hygiene, stmt_expr_attributes)]
+
+use remain::sorted;
+
+enum E {
+ Aaa,
+ Bbb(u8, u8),
+ Ccc(u8),
+ Ddd { u: u8 },
+}
+
+fn main() {
+ let value = E::Aaa;
+
+ #[sorted]
+ match value {
+ E::Aaa => {}
+ #[unsorted]
+ E::Ccc(_) => {}
+ E::Ddd { u: _ } => {}
+ E::Bbb(_, _) => {}
+ }
+}
diff --git a/0.2.1/tests/ui/unsorted-match-unstable.stderr b/0.2.1/tests/ui/unsorted-match-unstable.stderr
new file mode 100644
index 0000000..6b4509e
--- /dev/null
+++ b/0.2.1/tests/ui/unsorted-match-unstable.stderr
@@ -0,0 +1,5 @@
+error: E::Bbb should sort before E::Ddd
+ --> $DIR/unsorted-match-unstable.rs:15:5
+ |
+15 | #[sorted]
+ | ^^^^^^^^^
diff --git a/0.2.1/tests/ui/unsorted-struct.rs b/0.2.1/tests/ui/unsorted-struct.rs
new file mode 100644
index 0000000..98793f5
--- /dev/null
+++ b/0.2.1/tests/ui/unsorted-struct.rs
@@ -0,0 +1,12 @@
+use remain::sorted;
+
+#[sorted]
+struct TestStruct {
+ d: usize,
+ #[unsorted]
+ c: usize,
+ a: usize,
+ b: usize,
+}
+
+fn main() {}
diff --git a/0.2.1/tests/ui/unsorted-struct.stderr b/0.2.1/tests/ui/unsorted-struct.stderr
new file mode 100644
index 0000000..84509d7
--- /dev/null
+++ b/0.2.1/tests/ui/unsorted-struct.stderr
@@ -0,0 +1,5 @@
+error: a should sort before d
+ --> $DIR/unsorted-struct.rs:8:5
+ |
+8 | a: usize,
+ | ^
diff --git a/tests/ui/unsupported.rs b/0.2.1/tests/ui/unsupported.rs
similarity index 100%
copy from tests/ui/unsupported.rs
copy to 0.2.1/tests/ui/unsupported.rs
diff --git a/tests/ui/unsupported.stderr b/0.2.1/tests/ui/unsupported.stderr
similarity index 100%
copy from tests/ui/unsupported.stderr
copy to 0.2.1/tests/ui/unsupported.stderr
diff --git a/0.2.1/tests/unstable.rs b/0.2.1/tests/unstable.rs
new file mode 100644
index 0000000..1c94950
--- /dev/null
+++ b/0.2.1/tests/unstable.rs
@@ -0,0 +1,66 @@
+#![allow(dead_code)]
+#![cfg(not(remain_stable_testing))]
+#![feature(proc_macro_hygiene, stmt_expr_attributes)]
+
+#[remain::sorted]
+#[derive(PartialEq)]
+pub enum TestEnum {
+ A,
+ #[remain::unsorted]
+ Ignored,
+ B,
+ C,
+ D,
+ __Nonexhaustive,
+}
+
+#[remain::sorted]
+#[derive(PartialEq)]
+pub struct TestStruct {
+ a: usize,
+ b: usize,
+ c: usize,
+ #[unsorted]
+ ignored: usize,
+ d: usize,
+}
+
+#[test]
+fn test_attrs() {
+ fn is_partial_eq<T: PartialEq>() -> bool {
+ true
+ }
+
+ assert!(is_partial_eq::<TestEnum>());
+ assert!(is_partial_eq::<TestStruct>());
+}
+
+#[test]
+fn test_let() {
+ let value = TestEnum::A;
+
+ #[remain::sorted]
+ let _ = match value {
+ TestEnum::A => {}
+ #[remain::unsorted]
+ TestEnum::Ignored => {}
+ TestEnum::B => {}
+ TestEnum::C => {}
+ _ => {}
+ };
+}
+
+#[test]
+fn test_match() {
+ let value = TestEnum::A;
+
+ #[remain::sorted]
+ match value {
+ TestEnum::A => {}
+ TestEnum::B => {}
+ #[unsorted]
+ TestEnum::Ignored => {}
+ TestEnum::C => {}
+ _ => {}
+ }
+}