aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorandroid-build-team Robot <android-build-team-robot@google.com>2021-06-21 15:07:01 +0000
committerandroid-build-team Robot <android-build-team-robot@google.com>2021-06-21 15:07:01 +0000
commit836b5eb2b45275b41b8b935fbccb8e5c9258ece6 (patch)
tree569392943172a9c10a478f649934e46de88a1c7e
parentdc3071e3317ddbac7633c7438ee98a41398e62af (diff)
parentedab50513c32a57737c2fdf60ab69b06867e6e06 (diff)
downloadremain-android12-mainline-ipsec-release.tar.gz
Change-Id: I11da2b620710904f487ed70bbc966562ab261b91
-rw-r--r--.cargo_vcs_info.json2
-rw-r--r--.github/workflows/ci.yml39
-rw-r--r--.travis.yml14
-rw-r--r--Android.bp46
-rw-r--r--Cargo.toml17
-rw-r--r--Cargo.toml.orig15
l---------LICENSE1
-rw-r--r--METADATA19
-rw-r--r--MODULE_LICENSE_APACHE20
-rw-r--r--OWNERS1
-rw-r--r--README.md11
-rw-r--r--src/atom.rs141
-rw-r--r--src/check.rs121
-rw-r--r--src/compare.rs81
-rw-r--r--src/emit.rs8
-rw-r--r--src/lib.rs24
-rw-r--r--src/parse.rs27
-rw-r--r--src/visit.rs22
-rw-r--r--tests/compiletest.rs2
-rw-r--r--tests/order.rs57
-rw-r--r--tests/stable.rs33
-rw-r--r--tests/ui/let-unstable.stderr2
-rw-r--r--tests/ui/match-unstable.stderr2
-rw-r--r--tests/ui/repeat.rs14
-rw-r--r--tests/ui/repeat.stderr5
-rw-r--r--tests/ui/unnamed-fields.stderr4
-rw-r--r--tests/ui/unsorted-enum.rs12
-rw-r--r--tests/ui/unsorted-enum.stderr5
-rw-r--r--tests/ui/unsorted-match-stable.rs20
-rw-r--r--tests/ui/unsorted-match-stable.stderr5
-rw-r--r--tests/ui/unsorted-match-unstable.rs23
-rw-r--r--tests/ui/unsorted-match-unstable.stderr7
-rw-r--r--tests/ui/unsorted-struct.rs12
-rw-r--r--tests/ui/unsorted-struct.stderr5
-rw-r--r--tests/unstable.rs25
35 files changed, 691 insertions, 131 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
index 149af46..1ddcd35 100644
--- a/.cargo_vcs_info.json
+++ b/.cargo_vcs_info.json
@@ -1,5 +1,5 @@
{
"git": {
- "sha1": "7e714dc8dd0e17b9e6383f164d5095e60370c482"
+ "sha1": "4d56185ca2c69113dc7815c2d268388c76d1aea4"
}
}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..bf8f426
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,39 @@
+name: CI
+
+on:
+ push:
+ pull_request:
+ schedule: [cron: "40 1 * * *"]
+
+jobs:
+ nightly:
+ name: Rust nightly
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: dtolnay/rust-toolchain@nightly
+ - run: cargo test
+
+ test:
+ name: Rust ${{matrix.rust}}
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ rust: [beta, stable, 1.36.0]
+ steps:
+ - uses: actions/checkout@v2
+ - uses: dtolnay/rust-toolchain@master
+ with:
+ toolchain: ${{matrix.rust}}
+ - run: cargo test
+ env:
+ RUSTFLAGS: --cfg remain_stable_testing
+
+ msrv:
+ name: Rust 1.31.0
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: dtolnay/rust-toolchain@1.31.0
+ - run: cargo check
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index fe51652..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,14 +0,0 @@
-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.31.0
- env: RUSTFLAGS='--cfg remain_stable_testing'
diff --git a/Android.bp b/Android.bp
index 5500c76..9ad1f08 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,13 +1,55 @@
-// This file is generated by cargo2android.py.
+// This file is generated by cargo2android.py --run --dependencies.
+
+package {
+ default_applicable_licenses: ["external_rust_crates_remain_license"],
+}
+
+// Added automatically by a large-scale-change that took the approach of
+// 'apply every license found to every target'. While this makes sure we respect
+// every license restriction, it may not be entirely correct.
+//
+// e.g. GPL in an MIT project might only apply to the contrib/ directory.
+//
+// Please consider splitting the single license below into multiple licenses,
+// taking care not to lose any license_kind information, and overriding the
+// default license using the 'licenses: [...]' property on targets as needed.
+//
+// For unused files, consider creating a 'fileGroup' with "//visibility:private"
+// to attach the license to, and including a comment whether the files may be
+// used in the current project.
+//
+// large-scale-change included anything that looked like it might be a license
+// text as a license_text. e.g. LICENSE, NOTICE, COPYING etc.
+//
+// Please consider removing redundant or irrelevant files from 'license_text:'.
+// See: http://go/android-license-faq
+license {
+ name: "external_rust_crates_remain_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ "SPDX-license-identifier-MIT",
+ ],
+ license_text: [
+ "LICENSE-APACHE",
+ "LICENSE-MIT",
+ ],
+}
rust_proc_macro {
name: "libremain",
crate_name: "remain",
srcs: ["src/lib.rs"],
edition: "2018",
- rlibs: [
+ rustlibs: [
"libproc_macro2",
"libquote",
"libsyn",
],
}
+
+// dependent_library ["feature_list"]
+// proc-macro2-1.0.18 "default,proc-macro"
+// quote-1.0.7 "default,proc-macro"
+// syn-1.0.33 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit,visit-mut"
+// unicode-xid-0.2.1 "default"
diff --git a/Cargo.toml b/Cargo.toml
index 4134e67..197d26d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,27 +13,30 @@
[package]
edition = "2018"
name = "remain"
-version = "0.1.3"
+version = "0.2.2"
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"
+[package.metadata.docs.rs]
+targets = ["x86_64-unknown-linux-gnu"]
[lib]
proc-macro = true
[dependencies.proc-macro2]
-version = "0.4"
+version = "1.0"
[dependencies.quote]
-version = "0.6"
+version = "1.0"
[dependencies.syn]
-version = "0.15"
+version = "1.0"
features = ["full", "visit-mut"]
-[dev-dependencies.select-rustc]
-version = "0.1"
+[dev-dependencies.rustversion]
+version = "1.0"
[dev-dependencies.trybuild]
-version = "1.0"
+version = "1.0.19"
+features = ["diff"]
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
index c635f7a..b3328e0 100644
--- a/Cargo.toml.orig
+++ b/Cargo.toml.orig
@@ -1,6 +1,6 @@
[package]
name = "remain"
-version = "0.1.3" # remember to update number in readme for major versions
+version = "0.2.2" # remember to update number in readme for major versions
authors = ["David Tolnay <dtolnay@gmail.com>"]
edition = "2018"
license = "MIT OR Apache-2.0"
@@ -13,10 +13,13 @@ readme = "README.md"
proc-macro = true
[dependencies]
-proc-macro2 = "0.4"
-quote = "0.6"
-syn = { version = "0.15", features = ["full", "visit-mut"] }
+proc-macro2 = "1.0"
+quote = "1.0"
+syn = { version = "1.0", features = ["full", "visit-mut"] }
[dev-dependencies]
-select-rustc = "0.1"
-trybuild = "1.0"
+rustversion = "1.0"
+trybuild = { version = "1.0.19", features = ["diff"] }
+
+[package.metadata.docs.rs]
+targets = ["x86_64-unknown-linux-gnu"]
diff --git a/LICENSE b/LICENSE
new file mode 120000
index 0000000..6b579aa
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1 @@
+LICENSE-APACHE \ No newline at end of file
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..620b97b
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,19 @@
+name: "remain"
+description: "Compile-time checks that an enum, struct, or match is written in sorted order."
+third_party {
+ url {
+ type: HOMEPAGE
+ value: "https://crates.io/crates/remain"
+ }
+ url {
+ type: ARCHIVE
+ value: "https://static.crates.io/crates/remain/remain-0.2.2.crate"
+ }
+ version: "0.2.2"
+ license_type: NOTICE
+ last_upgrade_date {
+ year: 2020
+ month: 7
+ day: 10
+ }
+}
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_APACHE2
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..46fc303
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1 @@
+include platform/prebuilts/rust:/OWNERS
diff --git a/README.md b/README.md
index c3a4877..2a1442f 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,10 @@
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)
+[<img alt="github" src="https://img.shields.io/badge/github-dtolnay/remain-8da0cb?style=for-the-badge&labelColor=555555&logo=github" height="20">](https://github.com/dtolnay/remain)
+[<img alt="crates.io" src="https://img.shields.io/crates/v/remain.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/remain)
+[<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-remain-66c2a5?style=for-the-badge&labelColor=555555&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K" height="20">](https://docs.rs/remain)
+[<img alt="build status" src="https://img.shields.io/github/workflow/status/dtolnay/remain/CI/master?style=for-the-badge" height="20">](https://github.com/dtolnay/remain/actions?query=branch%3Amaster)
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
@@ -11,7 +12,7 @@ order.
```toml
[dependencies]
-remain = "0.1"
+remain = "0.2"
```
## Syntax
@@ -39,7 +40,7 @@ pub enum Error {
#[remain::sorted]
#[derive(Debug)]
-pub enum Registers {
+pub struct Registers {
ax: u16,
cx: u16,
di: u16,
diff --git a/src/atom.rs b/src/atom.rs
new file mode 100644
index 0000000..bd1ef86
--- /dev/null
+++ b/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/src/check.rs b/src/check.rs
index 0995078..0643f5f 100644
--- a/src/check.rs
+++ b/src/check.rs
@@ -1,69 +1,119 @@
-use syn::{Arm, Ident, Result, Variant};
+use quote::quote;
+use std::cmp::Ordering;
+use syn::{Arm, Attribute, Ident, Result, Variant};
use syn::{Error, Field, Pat, PatIdent};
-use crate::compare::Path;
+use crate::compare::{cmp, Path, UnderscoreOrder};
use crate::format;
use crate::parse::Input::{self, *};
-pub fn sorted(input: Input) -> Result<()> {
+pub fn sorted(input: &mut Input) -> Result<()> {
let paths = match input {
- Enum(item) => collect_paths(item.variants)?,
- Struct(fields) => collect_paths(fields.named)?,
- Match(expr) | Let(expr) => collect_paths(expr.arms)?,
+ 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() {
- let cur = &paths[i];
- if *cur < paths[i - 1] {
- let lesser = cur;
- let correct_pos = paths[..i - 1].binary_search(cur).unwrap_err();
- let greater = &paths[correct_pos];
- return Err(format::error(lesser, greater));
+ if cmp(&paths[i], &paths[i - 1], mode) == Ordering::Less {
+ return Some(i);
}
}
- Ok(())
+ None
}
-fn collect_paths<I>(iter: I) -> Result<Vec<Path>>
+fn collect_paths<'a, I, P>(iter: I) -> Result<Vec<Path>>
where
- I: IntoIterator,
- I::Item: IntoPath,
+ I: IntoIterator<Item = &'a mut P>,
+ P: Sortable + 'a,
{
- iter.into_iter().map(IntoPath::into_path).collect()
+ iter.into_iter()
+ .filter_map(|item| {
+ if remove_unsorted_attr(item.attrs()) {
+ None
+ } else {
+ Some(item.to_path())
+ }
+ })
+ .collect()
}
-trait IntoPath {
- fn into_path(self) -> Result<Path>;
+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 IntoPath for Variant {
- fn into_path(self) -> Result<Path> {
+impl Sortable for Variant {
+ fn to_path(&self) -> Result<Path> {
Ok(Path {
- segments: vec![self.ident],
+ segments: vec![self.ident.clone()],
})
}
+ fn attrs(&mut self) -> &mut Vec<Attribute> {
+ &mut self.attrs
+ }
}
-impl IntoPath for Field {
- fn into_path(self) -> Result<Path> {
+impl Sortable for Field {
+ fn to_path(&self) -> Result<Path> {
Ok(Path {
- segments: vec![self.ident.expect("must be named field")],
+ segments: vec![self.ident.clone().expect("must be named field")],
})
}
+ fn attrs(&mut self) -> &mut Vec<Attribute> {
+ &mut self.attrs
+ }
}
-impl IntoPath for Arm {
- fn into_path(self) -> Result<Path> {
+impl Sortable for Arm {
+ fn to_path(&self) -> Result<Path> {
// Sort by just the first pat.
- let pat = self.pats.into_iter().next().expect("at least one 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)],
- 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::Ident(ref pat) if is_just_ident(pat) => vec![pat.ident.clone()],
other => {
let msg = "unsupported by #[remain::sorted]";
return Err(Error::new_spanned(other, msg));
@@ -72,10 +122,13 @@ impl IntoPath for Arm {
Ok(Path { segments })
}
+ fn attrs(&mut self) -> &mut Vec<Attribute> {
+ &mut self.attrs
+ }
}
-fn idents_of_path(path: syn::Path) -> Vec<Ident> {
- path.segments.into_iter().map(|seg| seg.ident).collect()
+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 {
diff --git a/src/compare.rs b/src/compare.rs
index 59f997d..3fa4198 100644
--- a/src/compare.rs
+++ b/src/compare.rs
@@ -1,45 +1,68 @@
use proc_macro2::Ident;
use std::cmp::Ordering;
-#[derive(PartialEq, Eq)]
-pub struct Path {
- pub segments: Vec<Ident>,
+use crate::atom::iter_atoms;
+
+#[derive(Copy, Clone, PartialEq)]
+pub enum UnderscoreOrder {
+ First,
+ Last,
}
-impl PartialOrd for Path {
- fn partial_cmp(&self, other: &Path) -> Option<Ordering> {
- Some(self.cmp(other))
- }
+pub struct Path {
+ pub segments: Vec<Ident>,
}
-impl Ord for Path {
- fn cmp(&self, other: &Path) -> Ordering {
- // Lexicographic ordering across path segments.
- for (lhs, rhs) in self.segments.iter().zip(&other.segments) {
- match cmp(&lhs.to_string(), &rhs.to_string()) {
- Ordering::Equal => {}
- non_eq => return non_eq,
- }
+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,
}
-
- self.segments.len().cmp(&other.segments.len())
}
+
+ lhs.segments.len().cmp(&rhs.segments.len())
}
-// TODO: more intelligent comparison
-// for example to handle numeric cases like E9 < E10.
-fn cmp(lhs: &str, rhs: &str) -> Ordering {
+fn cmp_segment(lhs: &str, rhs: &str, mode: UnderscoreOrder) -> Ordering {
// Sort `_` last.
- match (lhs == "_", rhs == "_") {
- (true, true) => return Ordering::Equal,
- (true, false) => return Ordering::Greater,
- (false, true) => return Ordering::Less,
- (false, false) => {}
+ match (lhs, rhs) {
+ ("_", "_") => return Ordering::Equal,
+ ("_", _) => return Ordering::Greater,
+ (_, "_") => return Ordering::Less,
+ (_, _) => {}
}
- let lhs = lhs.to_ascii_lowercase();
- let rhs = rhs.to_ascii_lowercase();
+ let mut lhs_atoms = iter_atoms(lhs);
+ let mut rhs_atoms = iter_atoms(rhs);
- // For now: asciibetical ordering.
- lhs.cmp(&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/src/emit.rs b/src/emit.rs
index d1ddda8..4a051ba 100644
--- a/src/emit.rs
+++ b/src/emit.rs
@@ -11,7 +11,7 @@ pub enum Kind {
Let,
}
-pub fn emit(err: Error, kind: Kind, original: TokenStream) -> TokenStream {
+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.
@@ -19,11 +19,11 @@ pub fn emit(err: Error, kind: Kind, original: TokenStream) -> TokenStream {
}
let err = err.to_compile_error();
- let original = proc_macro2::TokenStream::from(original);
+ let output = proc_macro2::TokenStream::from(output);
let expanded = match kind {
- Kind::Enum | Kind::Let | Kind::Struct => quote!(#err #original),
- Kind::Match => quote!({ #err #original }),
+ Kind::Enum | Kind::Let | Kind::Struct => quote!(#err #output),
+ Kind::Match => quote!({ #err #output }),
};
TokenStream::from(expanded)
diff --git a/src/lib.rs b/src/lib.rs
index 967143e..c5c50bf 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,3 +1,11 @@
+//! [![github]](https://github.com/dtolnay/remain)&ensp;[![crates-io]](https://crates.io/crates/remain)&ensp;[![docs-rs]](https://docs.rs/remain)
+//!
+//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
+//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
+//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K
+//!
+//! <br>
+//!
//! 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.
@@ -135,8 +143,11 @@
//! # fn main() {}
//! ```
+#![allow(clippy::needless_doctest_main)]
+
extern crate proc_macro;
+mod atom;
mod check;
mod compare;
mod emit;
@@ -153,15 +164,16 @@ use crate::parse::{Input, Nothing};
#[proc_macro_attribute]
pub fn sorted(args: TokenStream, input: TokenStream) -> TokenStream {
- let original = input.clone();
-
let _ = parse_macro_input!(args as Nothing);
- let input = parse_macro_input!(input as Input);
+ let mut input = parse_macro_input!(input as Input);
let kind = input.kind();
- match check::sorted(input) {
- Ok(()) => original,
- Err(err) => emit(err, kind, original),
+ let result = check::sorted(&mut input);
+ let output = TokenStream::from(quote!(#input));
+
+ match result {
+ Ok(_) => output,
+ Err(err) => emit(err, kind, output),
}
}
diff --git a/src/parse.rs b/src/parse.rs
index 97bd9f8..aff4972 100644
--- a/src/parse.rs
+++ b/src/parse.rs
@@ -1,4 +1,5 @@
-use proc_macro2::Span;
+use proc_macro2::{Span, TokenStream};
+use quote::ToTokens;
use syn::parse::{Parse, ParseStream};
use syn::{Attribute, Error, Expr, Fields, Result, Stmt, Token, Visibility};
@@ -15,7 +16,7 @@ impl Parse for Nothing {
pub enum Input {
Enum(syn::ItemEnum),
Match(syn::ExprMatch),
- Struct(syn::FieldsNamed),
+ Struct(syn::ItemStruct),
Let(syn::ExprMatch),
}
@@ -32,9 +33,10 @@ impl Input {
impl Parse for Input {
fn parse(input: ParseStream) -> Result<Self> {
- let _ = input.call(Attribute::parse_outer)?;
+ let ahead = input.fork();
+ let _ = ahead.call(Attribute::parse_outer)?;
- if input.peek(Token![match]) {
+ if ahead.peek(Token![match]) {
let expr = match input.parse()? {
Expr::Match(expr) => expr,
_ => unreachable!("expected match"),
@@ -42,7 +44,7 @@ impl Parse for Input {
return Ok(Input::Match(expr));
}
- if input.peek(Token![let]) {
+ if ahead.peek(Token![let]) {
let stmt = match input.parse()? {
Stmt::Local(stmt) => stmt,
_ => unreachable!("expected let"),
@@ -58,14 +60,13 @@ impl Parse for Input {
return Ok(Input::Let(expr));
}
- let ahead = input.fork();
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(fields) = input.fields {
- return Ok(Input::Struct(fields));
+ if let Fields::Named(_) = &input.fields {
+ return Ok(Input::Struct(input));
}
}
@@ -73,6 +74,16 @@ impl Parse for Input {
}
}
+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";
diff --git a/src/visit.rs b/src/visit.rs
index f681aa9..f5cecbd 100644
--- a/src/visit.rs
+++ b/src/visit.rs
@@ -64,14 +64,16 @@ fn take_sorted_attr(attrs: &mut Vec<Attribute>) -> bool {
}
fn check_and_insert_error(input: ExprMatch, out: &mut Expr) {
- let original = quote!(#input);
- let input = Input::Match(input);
-
- if let Err(err) = crate::check::sorted(input) {
- let err = err.to_compile_error();
- *out = parse_quote!({
- #err
- #original
- });
- }
+ 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/tests/compiletest.rs b/tests/compiletest.rs
index 2e861bf..f9aea23 100644
--- a/tests/compiletest.rs
+++ b/tests/compiletest.rs
@@ -1,4 +1,4 @@
-#[rustc::attr(not(nightly), ignore)]
+#[rustversion::attr(not(nightly), ignore)]
#[test]
fn ui() {
let t = trybuild::TestCases::new();
diff --git a/tests/order.rs b/tests/order.rs
new file mode 100644
index 0000000..4723f94
--- /dev/null
+++ b/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/tests/stable.rs b/tests/stable.rs
index a3b6a7b..b1bcd65 100644
--- a/tests/stable.rs
+++ b/tests/stable.rs
@@ -1,30 +1,55 @@
#![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_match() {
+fn test_let() {
let value = TestEnum::A;
#[sorted]
let _ = match value {
TestEnum::A => {}
+ #[remain::unsorted]
+ TestEnum::Ignored => {}
TestEnum::B => {}
+ #[unsorted]
+ TestEnum::AlsoIgnored => {}
TestEnum::C => {}
_ => {}
};
@@ -32,14 +57,18 @@ fn test_match() {
#[test]
#[remain::check]
-fn test_let() {
+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/let-unstable.stderr b/tests/ui/let-unstable.stderr
index 97c5ba1..0960dba 100644
--- a/tests/ui/let-unstable.stderr
+++ b/tests/ui/let-unstable.stderr
@@ -3,3 +3,5 @@ error: E::Bbb should sort before E::Ccc
|
15 | #[sorted]
| ^^^^^^^^^
+ |
+ = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/tests/ui/match-unstable.stderr b/tests/ui/match-unstable.stderr
index b2bdea0..56872fb 100644
--- a/tests/ui/match-unstable.stderr
+++ b/tests/ui/match-unstable.stderr
@@ -3,3 +3,5 @@ error: E::Bbb should sort before E::Ccc
|
15 | #[sorted]
| ^^^^^^^^^
+ |
+ = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/tests/ui/repeat.rs b/tests/ui/repeat.rs
new file mode 100644
index 0000000..a0fe0ec
--- /dev/null
+++ b/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/tests/ui/repeat.stderr b/tests/ui/repeat.stderr
new file mode 100644
index 0000000..4dbf277
--- /dev/null
+++ b/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/unnamed-fields.stderr b/tests/ui/unnamed-fields.stderr
index b7e28f5..618fb22 100644
--- a/tests/ui/unnamed-fields.stderr
+++ b/tests/ui/unnamed-fields.stderr
@@ -3,9 +3,13 @@ error: expected enum, struct, or match expression
|
1 | #[remain::sorted]
| ^^^^^^^^^^^^^^^^^
+ |
+ = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: expected enum, struct, or match expression
--> $DIR/unnamed-fields.rs:4:1
|
4 | #[remain::sorted]
| ^^^^^^^^^^^^^^^^^
+ |
+ = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/tests/ui/unsorted-enum.rs b/tests/ui/unsorted-enum.rs
new file mode 100644
index 0000000..f4e299b
--- /dev/null
+++ b/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/tests/ui/unsorted-enum.stderr b/tests/ui/unsorted-enum.stderr
new file mode 100644
index 0000000..249d171
--- /dev/null
+++ b/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/tests/ui/unsorted-match-stable.rs b/tests/ui/unsorted-match-stable.rs
new file mode 100644
index 0000000..f85202f
--- /dev/null
+++ b/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/tests/ui/unsorted-match-stable.stderr b/tests/ui/unsorted-match-stable.stderr
new file mode 100644
index 0000000..d04d317
--- /dev/null
+++ b/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/tests/ui/unsorted-match-unstable.rs b/tests/ui/unsorted-match-unstable.rs
new file mode 100644
index 0000000..b3e95a6
--- /dev/null
+++ b/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/tests/ui/unsorted-match-unstable.stderr b/tests/ui/unsorted-match-unstable.stderr
new file mode 100644
index 0000000..386860b
--- /dev/null
+++ b/tests/ui/unsorted-match-unstable.stderr
@@ -0,0 +1,7 @@
+error: E::Bbb should sort before E::Ddd
+ --> $DIR/unsorted-match-unstable.rs:15:5
+ |
+15 | #[sorted]
+ | ^^^^^^^^^
+ |
+ = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/tests/ui/unsorted-struct.rs b/tests/ui/unsorted-struct.rs
new file mode 100644
index 0000000..98793f5
--- /dev/null
+++ b/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/tests/ui/unsorted-struct.stderr b/tests/ui/unsorted-struct.stderr
new file mode 100644
index 0000000..84509d7
--- /dev/null
+++ b/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/unstable.rs b/tests/unstable.rs
index 78dbd08..1c94950 100644
--- a/tests/unstable.rs
+++ b/tests/unstable.rs
@@ -3,28 +3,47 @@
#![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_match() {
+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 => {}
_ => {}
@@ -32,13 +51,15 @@ fn test_match() {
}
#[test]
-fn test_let() {
+fn test_match() {
let value = TestEnum::A;
#[remain::sorted]
match value {
TestEnum::A => {}
TestEnum::B => {}
+ #[unsorted]
+ TestEnum::Ignored => {}
TestEnum::C => {}
_ => {}
}