aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorChih-Hung Hsieh <chh@google.com>2020-03-17 13:20:31 -0700
committerChih-Hung Hsieh <chh@google.com>2020-03-19 11:34:20 -0700
commit5846f731f46c552ac3a2dc8b38dea64df8aca81a (patch)
tree1b9f1909282fc1970ffd41958cc0188f03db2636 /src
parent3095d1536defaeecd3bda0e10483589a2ebc1428 (diff)
downloadremain-5846f731f46c552ac3a2dc8b38dea64df8aca81a.tar.gz
Remove old 0.1.3; used only by old crosvm.
* 0.2.1 becomes the default Test: make Bug: 151628085 Change-Id: If095bf4f0fa9e33269df62fc346d5b4d75b0e5aa
Diffstat (limited to 'src')
-rw-r--r--src/atom.rs141
-rw-r--r--src/check.rs136
-rw-r--r--src/compare.rs68
-rw-r--r--src/emit.rs39
-rw-r--r--src/format.rs27
-rw-r--r--src/lib.rs180
-rw-r--r--src/parse.rs91
-rw-r--r--src/visit.rs79
8 files changed, 761 insertions, 0 deletions
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
new file mode 100644
index 0000000..0643f5f
--- /dev/null
+++ b/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/src/compare.rs b/src/compare.rs
new file mode 100644
index 0000000..3fa4198
--- /dev/null
+++ b/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/src/emit.rs b/src/emit.rs
new file mode 100644
index 0000000..4a051ba
--- /dev/null
+++ b/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/src/format.rs
new file mode 100644
index 0000000..5832643
--- /dev/null
+++ b/src/format.rs
@@ -0,0 +1,27 @@
+use proc_macro2::TokenStream;
+use quote::TokenStreamExt;
+use std::fmt::{self, Display};
+use syn::Error;
+
+use crate::compare::Path;
+
+impl Display for Path {
+ fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ for (i, segment) in self.segments.iter().enumerate() {
+ if i > 0 {
+ formatter.write_str("::")?;
+ }
+ segment.fmt(formatter)?;
+ }
+ Ok(())
+ }
+}
+
+pub fn error(lesser: &Path, greater: &Path) -> Error {
+ let mut spans = TokenStream::new();
+ spans.append_all(&lesser.segments);
+
+ let msg = format!("{} should sort before {}", lesser, greater);
+
+ Error::new_spanned(spans, msg)
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..5648e28
--- /dev/null
+++ b/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/src/parse.rs b/src/parse.rs
new file mode 100644
index 0000000..aff4972
--- /dev/null
+++ b/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/src/visit.rs b/src/visit.rs
new file mode 100644
index 0000000..f5cecbd
--- /dev/null
+++ b/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
+ })
+ }
+ };
+}