Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support string literal patterns #14

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[toolchain]
channel = "nightly"
components = [ "rustfmt", "clippy" ]
73 changes: 43 additions & 30 deletions src/check.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -34,7 +34,7 @@ pub fn sorted(input: &mut Input) -> Result<()> {
Err(format::error(lesser, greater))
}

fn find_misordered(paths: &[Path], mode: UnderscoreOrder) -> Option<usize> {
fn find_misordered(paths: &[Comparable], mode: UnderscoreOrder) -> Option<usize> {
for i in 1..paths.len() {
if cmp(&paths[i], &paths[i - 1], mode) == Ordering::Less {
return Some(i);
Expand All @@ -44,7 +44,7 @@ fn find_misordered(paths: &[Path], mode: UnderscoreOrder) -> Option<usize> {
None
}

fn collect_paths<'a, I, P>(iter: I) -> Result<Vec<Path>>
fn collect_comparables<'a, I, P>(iter: I) -> Result<Vec<Comparable>>
where
I: IntoIterator<Item = &'a mut P>,
P: Sortable + 'a,
Expand Down Expand Up @@ -74,61 +74,74 @@ fn remove_unsorted_attr(attrs: &mut Vec<Attribute>) -> bool {
}

trait Sortable {
fn to_path(&self) -> Result<Path>;
fn to_path(&self) -> Result<Comparable>;
fn attrs(&mut self) -> &mut Vec<Attribute>;
}

impl Sortable for Variant {
fn to_path(&self) -> Result<Path> {
Ok(Path {
segments: vec![self.ident.clone()],
})
fn to_path(&self) -> Result<Comparable> {
Ok(Comparable::of(self.ident.clone()))
}
fn attrs(&mut self) -> &mut Vec<Attribute> {
&mut self.attrs
}
}

impl Sortable for Field {
fn to_path(&self) -> Result<Path> {
Ok(Path {
segments: vec![self.ident.clone().expect("must be named field")],
})
fn to_path(&self) -> Result<Comparable> {
Ok(Comparable::of(
self.ident.as_ref().expect("must be named field").clone(),
))
}
fn attrs(&mut self) -> &mut Vec<Attribute> {
&mut self.attrs
}
}

impl Sortable for Arm {
fn to_path(&self) -> Result<Path> {
fn to_path(&self) -> Result<Comparable> {
// 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<Comparable> = 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<Attribute> {
&mut self.attrs
}
}

fn idents_of_path(path: &syn::Path) -> Vec<Ident> {
path.segments.iter().map(|seg| seg.ident.clone()).collect()
fn comparables_of_path(path: &syn::Path) -> Comparable {
let mut segments: Vec<Box<dyn Segment>> = vec![];

for seg in path.segments.iter() {
segments.push(Box::new(seg.ident.clone()));
}

Comparable { segments }
}

fn is_just_ident(pat: &PatIdent) -> bool {
Expand Down
38 changes: 35 additions & 3 deletions src/compare.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use proc_macro2::Ident;
use std::cmp::Ordering;
use syn::LitStr;

use crate::atom::iter_atoms;

Expand All @@ -9,11 +10,42 @@ pub enum UnderscoreOrder {
Last,
}

pub struct Path {
pub segments: Vec<Ident>,
pub struct Comparable {
pub segments: Vec<Box<dyn Segment>>,
}

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) {
Expand Down
8 changes: 4 additions & 4 deletions src/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
33 changes: 33 additions & 0 deletions tests/ui/match-mixed-stable.rs
Original file line number Diff line number Diff line change
@@ -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" => {}
_ => {}
}
}
5 changes: 5 additions & 0 deletions tests/ui/match-mixed-stable.stderr
Original file line number Diff line number Diff line change
@@ -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" => {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
36 changes: 36 additions & 0 deletions tests/ui/match-mixed-unstable.rs
Original file line number Diff line number Diff line change
@@ -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" => {}
_ => {}
}
}
7 changes: 7 additions & 0 deletions tests/ui/match-mixed-unstable.stderr
Original file line number Diff line number Diff line change
@@ -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)