diff options
author | android-build-team Robot <android-build-team-robot@google.com> | 2021-06-21 15:07:01 +0000 |
---|---|---|
committer | android-build-team Robot <android-build-team-robot@google.com> | 2021-06-21 15:07:01 +0000 |
commit | 836b5eb2b45275b41b8b935fbccb8e5c9258ece6 (patch) | |
tree | 569392943172a9c10a478f649934e46de88a1c7e | |
parent | dc3071e3317ddbac7633c7438ee98a41398e62af (diff) | |
parent | edab50513c32a57737c2fdf60ab69b06867e6e06 (diff) | |
download | remain-android12-mainline-ipsec-release.tar.gz |
Snap for 7478067 from edab50513c32a57737c2fdf60ab69b06867e6e06 to mainline-ipsec-releaseandroid-mainline-12.0.0_r88android-mainline-12.0.0_r73android-mainline-12.0.0_r61android-mainline-12.0.0_r45android-mainline-12.0.0_r28android-mainline-12.0.0_r118android-mainline-12.0.0_r103android-mainline-12.0.0_r10android12-mainline-ipsec-release
Change-Id: I11da2b620710904f487ed70bbc966562ab261b91
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' @@ -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" @@ -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"] @@ -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 @@ -0,0 +1 @@ +include platform/prebuilts/rust:/OWNERS @@ -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) @@ -1,3 +1,11 @@ +//! [![github]](https://github.com/dtolnay/remain) [![crates-io]](https://crates.io/crates/remain) [![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 => {} _ => {} } |