aboutsummaryrefslogtreecommitdiff
path: root/src/check.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/check.rs')
-rw-r--r--src/check.rs83
1 files changed, 83 insertions, 0 deletions
diff --git a/src/check.rs b/src/check.rs
new file mode 100644
index 0000000..0995078
--- /dev/null
+++ b/src/check.rs
@@ -0,0 +1,83 @@
+use syn::{Arm, Ident, Result, Variant};
+use syn::{Error, Field, Pat, PatIdent};
+
+use crate::compare::Path;
+use crate::format;
+use crate::parse::Input::{self, *};
+
+pub fn sorted(input: 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)?,
+ };
+
+ 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));
+ }
+ }
+
+ Ok(())
+}
+
+fn collect_paths<I>(iter: I) -> Result<Vec<Path>>
+where
+ I: IntoIterator,
+ I::Item: IntoPath,
+{
+ iter.into_iter().map(IntoPath::into_path).collect()
+}
+
+trait IntoPath {
+ fn into_path(self) -> Result<Path>;
+}
+
+impl IntoPath for Variant {
+ fn into_path(self) -> Result<Path> {
+ Ok(Path {
+ segments: vec![self.ident],
+ })
+ }
+}
+
+impl IntoPath for Field {
+ fn into_path(self) -> Result<Path> {
+ Ok(Path {
+ segments: vec![self.ident.expect("must be named field")],
+ })
+ }
+}
+
+impl IntoPath for Arm {
+ fn into_path(self) -> Result<Path> {
+ // Sort by just the first pat.
+ let pat = self.pats.into_iter().next().expect("at least one pat");
+
+ let segments = match pat {
+ 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));
+ }
+ };
+
+ Ok(Path { segments })
+ }
+}
+
+fn idents_of_path(path: syn::Path) -> Vec<Ident> {
+ path.segments.into_iter().map(|seg| seg.ident).collect()
+}
+
+fn is_just_ident(pat: &PatIdent) -> bool {
+ pat.by_ref.is_none() && pat.mutability.is_none() && pat.subpat.is_none()
+}