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 { 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> where I: IntoIterator, 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) -> 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; fn attrs(&mut self) -> &mut Vec; } impl Sortable for Variant { fn to_path(&self) -> Result { Ok(Path { segments: vec![self.ident.clone()], }) } fn attrs(&mut self) -> &mut Vec { &mut self.attrs } } impl Sortable for Field { fn to_path(&self) -> Result { Ok(Path { segments: vec![self.ident.clone().expect("must be named field")], }) } fn attrs(&mut self) -> &mut Vec { &mut self.attrs } } impl Sortable for Arm { fn to_path(&self) -> Result { // 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 { &mut self.attrs } } fn idents_of_path(path: &syn::Path) -> Vec { 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() }