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 => {}
+        _ => {}
+    }
+}