blob: 0643f5f1ba50d9dca1c2ec995af41f0799c74881 [file] [log] [blame]
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()
}