aboutsummaryrefslogtreecommitdiff
path: root/src/check.rs
blob: 0995078b4db9b7d741dda6bcba6c28f5d635139b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
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()
}