diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..e9d4cf2 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "nightly" +components = [ "rustfmt", "clippy" ] diff --git a/src/check.rs b/src/check.rs index 39a23e5..3006127 100644 --- a/src/check.rs +++ b/src/check.rs @@ -1,17 +1,17 @@ use quote::quote; use std::cmp::Ordering; -use syn::{Arm, Attribute, Ident, Result, Variant}; +use syn::{Arm, Attribute, Result, Variant}; use syn::{Error, Field, Pat, PatIdent}; -use crate::compare::{cmp, Path, UnderscoreOrder}; +use crate::compare::{cmp, Comparable, Segment, 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)?, + Enum(item) => collect_comparables(&mut item.variants)?, + Struct(item) => collect_comparables(&mut item.fields)?, + Match(expr) | Let(expr) => collect_comparables(&mut expr.arms)?, }; let mode = UnderscoreOrder::First; @@ -34,7 +34,7 @@ pub fn sorted(input: &mut Input) -> Result<()> { Err(format::error(lesser, greater)) } -fn find_misordered(paths: &[Path], mode: UnderscoreOrder) -> Option { +fn find_misordered(paths: &[Comparable], mode: UnderscoreOrder) -> Option { for i in 1..paths.len() { if cmp(&paths[i], &paths[i - 1], mode) == Ordering::Less { return Some(i); @@ -44,7 +44,7 @@ fn find_misordered(paths: &[Path], mode: UnderscoreOrder) -> Option { None } -fn collect_paths<'a, I, P>(iter: I) -> Result> +fn collect_comparables<'a, I, P>(iter: I) -> Result> where I: IntoIterator, P: Sortable + 'a, @@ -74,15 +74,13 @@ fn remove_unsorted_attr(attrs: &mut Vec) -> bool { } trait Sortable { - fn to_path(&self) -> Result; + 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 to_path(&self) -> Result { + Ok(Comparable::of(self.ident.clone())) } fn attrs(&mut self) -> &mut Vec { &mut self.attrs @@ -90,10 +88,10 @@ impl Sortable for Variant { } impl Sortable for Field { - fn to_path(&self) -> Result { - Ok(Path { - segments: vec![self.ident.clone().expect("must be named field")], - }) + fn to_path(&self) -> Result { + Ok(Comparable::of( + self.ident.as_ref().expect("must be named field").clone(), + )) } fn attrs(&mut self) -> &mut Vec { &mut self.attrs @@ -101,34 +99,49 @@ impl Sortable for Field { } impl Sortable for Arm { - fn to_path(&self) -> Result { + 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)); - } + let segments: Option = match pat { + Pat::Lit(pat_lit) => match pat_lit.expr.as_ref() { + syn::Expr::Lit(lit) => match &lit.lit { + syn::Lit::Str(s) => Some(Comparable::of(s.clone())), + _ => None, + }, + _ => None, + }, + Pat::Ident(pat) if is_just_ident(pat) => Some(Comparable::of(pat.ident.clone())), + Pat::Path(pat) => Some(comparables_of_path(&pat.path)), + Pat::Struct(pat) => Some(comparables_of_path(&pat.path)), + Pat::TupleStruct(pat) => Some(comparables_of_path(&pat.path)), + Pat::Wild(pat) => Some(Comparable::of(pat.underscore_token)), + _ => None, }; - Ok(Path { segments }) + if let Some(segments) = segments { + Ok(segments) + } else { + let msg = "unsupported by #[remain::sorted]"; + Err(Error::new_spanned(pat, msg)) + } } 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 comparables_of_path(path: &syn::Path) -> Comparable { + let mut segments: Vec> = vec![]; + + for seg in path.segments.iter() { + segments.push(Box::new(seg.ident.clone())); + } + + Comparable { segments } } fn is_just_ident(pat: &PatIdent) -> bool { diff --git a/src/compare.rs b/src/compare.rs index 3fa4198..84fbdb3 100644 --- a/src/compare.rs +++ b/src/compare.rs @@ -1,5 +1,6 @@ use proc_macro2::Ident; use std::cmp::Ordering; +use syn::LitStr; use crate::atom::iter_atoms; @@ -9,11 +10,42 @@ pub enum UnderscoreOrder { Last, } -pub struct Path { - pub segments: Vec, +pub struct Comparable { + pub segments: Vec>, } -pub fn cmp(lhs: &Path, rhs: &Path, mode: UnderscoreOrder) -> Ordering { +impl Comparable { + pub fn of(segment: impl Segment + 'static) -> Comparable { + Comparable { + segments: vec![Box::new(segment)], + } + } +} + +pub trait Segment: quote::ToTokens { + /// The string representation of these tokens to be used for sorting. + fn to_string(&self) -> String; +} + +impl Segment for Ident { + fn to_string(&self) -> String { + ToString::to_string(&self) + } +} + +impl Segment for LitStr { + fn to_string(&self) -> String { + self.value() + } +} + +impl Segment for syn::token::Underscore { + fn to_string(&self) -> String { + "_".to_string() + } +} + +pub fn cmp(lhs: &Comparable, rhs: &Comparable, mode: UnderscoreOrder) -> Ordering { // Lexicographic ordering across path segments. for (lhs, rhs) in lhs.segments.iter().zip(&rhs.segments) { match cmp_segment(&lhs.to_string(), &rhs.to_string(), mode) { diff --git a/src/format.rs b/src/format.rs index 5832643..62bd08e 100644 --- a/src/format.rs +++ b/src/format.rs @@ -3,21 +3,21 @@ use quote::TokenStreamExt; use std::fmt::{self, Display}; use syn::Error; -use crate::compare::Path; +use crate::compare::Comparable; -impl Display for Path { +impl Display for Comparable { fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { for (i, segment) in self.segments.iter().enumerate() { if i > 0 { formatter.write_str("::")?; } - segment.fmt(formatter)?; + segment.to_string().fmt(formatter)?; } Ok(()) } } -pub fn error(lesser: &Path, greater: &Path) -> Error { +pub fn error(lesser: &Comparable, greater: &Comparable) -> Error { let mut spans = TokenStream::new(); spans.append_all(&lesser.segments); diff --git a/tests/ui/match-mixed-stable.rs b/tests/ui/match-mixed-stable.rs new file mode 100644 index 0000000..c5c9097 --- /dev/null +++ b/tests/ui/match-mixed-stable.rs @@ -0,0 +1,33 @@ +#[remain::check] +fn main() { + let value = "trivial_regex"; + + #[sorted] + match value { + "cognitive_complexity" => {} + "hello world" => {} + "implicit_hasher" => {} + "inefficient_to_string" => {} + "integer_division" => {} + "large_digit_groups" => {} + let_it_be if false => {} + "let_unit_value" => {} + "manual_map" => {} + "match_bool" => {} + mixed_in if false => {} + "needless_pass_by_value" => {} + "new_ret_no_self" => {} + "nonstandard_macro_braces" => {} + "option_if_let_else" => {} + "option_option" => {} + "rc_buffer" => {} + "string_lit_as_bytes" => {} + "trivial_regex" => {} + "useless_let_if_seq" => {} + "trivially_copy_pass_by_ref" => {} + "unnested_or_patterns" => {} + "unreadable_literal" => {} + "unsafe_vector_initialization" => {} + _ => {} + } +} diff --git a/tests/ui/match-mixed-stable.stderr b/tests/ui/match-mixed-stable.stderr new file mode 100644 index 0000000..90c919b --- /dev/null +++ b/tests/ui/match-mixed-stable.stderr @@ -0,0 +1,5 @@ +error: trivially_copy_pass_by_ref should sort before useless_let_if_seq + --> $DIR/match-mixed-stable.rs:27:9 + | +27 | "trivially_copy_pass_by_ref" => {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/match-mixed-unstable.rs b/tests/ui/match-mixed-unstable.rs new file mode 100644 index 0000000..5b1d1f5 --- /dev/null +++ b/tests/ui/match-mixed-unstable.rs @@ -0,0 +1,36 @@ +#![feature(proc_macro_hygiene, stmt_expr_attributes)] + +use remain::sorted; + +fn main() { + let value = "trivial_regex"; + + #[sorted] + match value { + "cognitive_complexity" => {} + "hello world" => {} + "implicit_hasher" => {} + "inefficient_to_string" => {} + "integer_division" => {} + "large_digit_groups" => {} + let_it_be if false => {} + "let_unit_value" => {} + "manual_map" => {} + "match_bool" => {} + mixed_in if false => {} + "needless_pass_by_value" => {} + "new_ret_no_self" => {} + "nonstandard_macro_braces" => {} + "option_if_let_else" => {} + "option_option" => {} + "rc_buffer" => {} + "string_lit_as_bytes" => {} + "trivial_regex" => {} + "useless_let_if_seq" => {} + "trivially_copy_pass_by_ref" => {} + "unnested_or_patterns" => {} + "unreadable_literal" => {} + "unsafe_vector_initialization" => {} + _ => {} + } +} diff --git a/tests/ui/match-mixed-unstable.stderr b/tests/ui/match-mixed-unstable.stderr new file mode 100644 index 0000000..86e98cf --- /dev/null +++ b/tests/ui/match-mixed-unstable.stderr @@ -0,0 +1,7 @@ +error: trivially_copy_pass_by_ref should sort before useless_let_if_seq + --> $DIR/match-mixed-unstable.rs:8:5 + | +8 | #[sorted] + | ^^^^^^^^^ + | + = note: this error originates in the attribute macro `sorted` (in Nightly builds, run with -Z macro-backtrace for more info)