1 use quote::quote;
2 use std::cmp::Ordering;
3 use syn::{Arm, Attribute, Ident, Result, Variant};
4 use syn::{Error, Field, Pat, PatIdent};
5
6 use crate::compare::{cmp, Path, UnderscoreOrder};
7 use crate::format;
8 use crate::parse::Input::{self, *};
9
sorted(input: &mut Input) -> Result<()>10 pub fn sorted(input: &mut Input) -> Result<()> {
11 let paths = match input {
12 Enum(item) => collect_paths(&mut item.variants)?,
13 Struct(item) => collect_paths(&mut item.fields)?,
14 Match(expr) | Let(expr) => collect_paths(&mut expr.arms)?,
15 };
16
17 let mode = UnderscoreOrder::First;
18 if find_misordered(&paths, mode).is_none() {
19 return Ok(());
20 }
21
22 let mode = UnderscoreOrder::Last;
23 let wrong = match find_misordered(&paths, mode) {
24 Some(wrong) => wrong,
25 None => return Ok(()),
26 };
27
28 let lesser = &paths[wrong];
29 let correct_pos = match paths[..wrong - 1].binary_search_by(|probe| cmp(probe, lesser, mode)) {
30 Err(correct_pos) => correct_pos,
31 Ok(equal_to) => equal_to + 1,
32 };
33 let greater = &paths[correct_pos];
34 Err(format::error(lesser, greater))
35 }
36
find_misordered(paths: &[Path], mode: UnderscoreOrder) -> Option<usize>37 fn find_misordered(paths: &[Path], mode: UnderscoreOrder) -> Option<usize> {
38 for i in 1..paths.len() {
39 if cmp(&paths[i], &paths[i - 1], mode) == Ordering::Less {
40 return Some(i);
41 }
42 }
43
44 None
45 }
46
collect_paths<'a, I, P>(iter: I) -> Result<Vec<Path>> where I: IntoIterator<Item = &'a mut P>, P: Sortable + 'a,47 fn collect_paths<'a, I, P>(iter: I) -> Result<Vec<Path>>
48 where
49 I: IntoIterator<Item = &'a mut P>,
50 P: Sortable + 'a,
51 {
52 iter.into_iter()
53 .filter_map(|item| {
54 if remove_unsorted_attr(item.attrs()) {
55 None
56 } else {
57 Some(item.to_path())
58 }
59 })
60 .collect()
61 }
62
remove_unsorted_attr(attrs: &mut Vec<Attribute>) -> bool63 fn remove_unsorted_attr(attrs: &mut Vec<Attribute>) -> bool {
64 for i in 0..attrs.len() {
65 let path = &attrs[i].path;
66 let path = quote!(#path).to_string();
67 if path == "unsorted" || path == "remain :: unsorted" {
68 attrs.remove(i);
69 return true;
70 }
71 }
72
73 false
74 }
75
76 trait Sortable {
to_path(&self) -> Result<Path>77 fn to_path(&self) -> Result<Path>;
attrs(&mut self) -> &mut Vec<Attribute>78 fn attrs(&mut self) -> &mut Vec<Attribute>;
79 }
80
81 impl Sortable for Variant {
to_path(&self) -> Result<Path>82 fn to_path(&self) -> Result<Path> {
83 Ok(Path {
84 segments: vec![self.ident.clone()],
85 })
86 }
attrs(&mut self) -> &mut Vec<Attribute>87 fn attrs(&mut self) -> &mut Vec<Attribute> {
88 &mut self.attrs
89 }
90 }
91
92 impl Sortable for Field {
to_path(&self) -> Result<Path>93 fn to_path(&self) -> Result<Path> {
94 Ok(Path {
95 segments: vec![self.ident.clone().expect("must be named field")],
96 })
97 }
attrs(&mut self) -> &mut Vec<Attribute>98 fn attrs(&mut self) -> &mut Vec<Attribute> {
99 &mut self.attrs
100 }
101 }
102
103 impl Sortable for Arm {
to_path(&self) -> Result<Path>104 fn to_path(&self) -> Result<Path> {
105 // Sort by just the first pat.
106 let pat = match &self.pat {
107 Pat::Or(pat) => pat.cases.iter().next().expect("at least one pat"),
108 _ => &self.pat,
109 };
110
111 let segments = match pat {
112 Pat::Ident(pat) if is_just_ident(pat) => vec![pat.ident.clone()],
113 Pat::Path(pat) => idents_of_path(&pat.path),
114 Pat::Struct(pat) => idents_of_path(&pat.path),
115 Pat::TupleStruct(pat) => idents_of_path(&pat.path),
116 Pat::Wild(pat) => vec![Ident::from(pat.underscore_token)],
117 other => {
118 let msg = "unsupported by #[remain::sorted]";
119 return Err(Error::new_spanned(other, msg));
120 }
121 };
122
123 Ok(Path { segments })
124 }
attrs(&mut self) -> &mut Vec<Attribute>125 fn attrs(&mut self) -> &mut Vec<Attribute> {
126 &mut self.attrs
127 }
128 }
129
idents_of_path(path: &syn::Path) -> Vec<Ident>130 fn idents_of_path(path: &syn::Path) -> Vec<Ident> {
131 path.segments.iter().map(|seg| seg.ident.clone()).collect()
132 }
133
is_just_ident(pat: &PatIdent) -> bool134 fn is_just_ident(pat: &PatIdent) -> bool {
135 pat.by_ref.is_none() && pat.mutability.is_none() && pat.subpat.is_none()
136 }
137