From 7ea31f72e819c55563b60d994d1337a3c122a59f Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Sat, 1 Oct 2022 11:45:00 -0400 Subject: [PATCH 01/10] Add helper function to determine if a TokenTree is a comma --- src/parse/macros/mod.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/parse/macros/mod.rs b/src/parse/macros/mod.rs index 8a956faf03b..f898a31359d 100644 --- a/src/parse/macros/mod.rs +++ b/src/parse/macros/mod.rs @@ -1,5 +1,5 @@ use rustc_ast::token::{Delimiter, NonterminalKind, NtExprKind::*, NtPatKind::*, TokenKind}; -use rustc_ast::tokenstream::TokenStream; +use rustc_ast::tokenstream::{TokenStream, TokenTree}; use rustc_ast::{ast, ptr}; use rustc_parse::MACRO_ARGUMENTS; use rustc_parse::parser::{ForceCollect, Parser, Recovery}; @@ -96,6 +96,11 @@ fn check_keyword<'a, 'b: 'a>(parser: &'a mut Parser<'b>) -> Option { } } +/// Helper function to determine if a tokentree is a comma +pub(crate) fn is_token_tree_comma(tt: &TokenTree) -> bool { + matches!(tt, TokenTree::Token(token, _) if token.kind == TokenKind::Comma) +} + pub(crate) fn parse_macro_args( context: &RewriteContext<'_>, tokens: TokenStream, From 8a9317be6225b341e53ffa398d699739d81512ba Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Fri, 30 Sep 2022 18:16:46 -0400 Subject: [PATCH 02/10] Parse `matches!` macro The first step in being able to format the `matches!` macro is to parse it into ast nodes that we know how to rewrite. --- src/parse/macros/matches.rs | 54 +++++++++++++++++++++++++++++++++++++ src/parse/macros/mod.rs | 1 + 2 files changed, 55 insertions(+) create mode 100644 src/parse/macros/matches.rs diff --git a/src/parse/macros/matches.rs b/src/parse/macros/matches.rs new file mode 100644 index 00000000000..f2d05069d20 --- /dev/null +++ b/src/parse/macros/matches.rs @@ -0,0 +1,54 @@ +use rustc_ast::ast; +use rustc_ast::ptr::P; +use rustc_ast::token::TokenKind; +use rustc_ast::tokenstream::TokenStream; +use rustc_parse::parser::{CommaRecoveryMode, RecoverColon, RecoverComma}; +use rustc_span::symbol::kw; + +use super::is_token_tree_comma; +use crate::rewrite::RewriteContext; + +#[derive(Debug)] +pub(crate) struct Matches { + pub(crate) expr: P, + pub(crate) pat: P, + pub(crate) guard: Option>, +} + +/// Parse matches! from +pub(crate) fn parse_matches(context: &RewriteContext<'_>, ts: TokenStream) -> Option { + let mut cursor = ts.trees().peekable(); + // remove trailing commmas from the TokenStream since they lead to errors when parsing ast::Pat + // using parse_pat_allow_top_alt below since the parser isn't expecting a trailing comma. + // This is only an issue when the `ast::Pat` is not followed by a guard. In either case it's ok + // to remove the comma from the stream since we don't need it to parse into a Matches struct + let mut token_trees = vec![]; + while let Some(tt) = cursor.next() { + let is_last = cursor.peek().is_none(); + if !(is_last && is_token_tree_comma(tt)) { + token_trees.push(tt.clone()) + } + } + + let ts = token_trees.into_iter().collect(); + let mut parser = super::build_parser(context, ts); + let expr = parser.parse_expr().ok()?; + + parser.eat(&TokenKind::Comma); + + let pat = parser + .parse_pat_allow_top_alt( + None, + RecoverComma::Yes, + RecoverColon::Yes, + CommaRecoveryMode::EitherTupleOrPipe, + ) + .ok()?; + + let guard = if parser.eat_keyword(kw::If) { + Some(parser.parse_expr().ok()?) + } else { + None + }; + Some(Matches { expr, pat, guard }) +} diff --git a/src/parse/macros/mod.rs b/src/parse/macros/mod.rs index f898a31359d..e279c85a592 100644 --- a/src/parse/macros/mod.rs +++ b/src/parse/macros/mod.rs @@ -13,6 +13,7 @@ pub(crate) mod asm; pub(crate) mod cfg_if; pub(crate) mod cfg_match; pub(crate) mod lazy_static; +pub(crate) mod matches; fn build_stream_parser<'a>(psess: &'a ParseSess, tokens: TokenStream) -> Parser<'a> { Parser::new(psess, tokens, MACRO_ARGUMENTS).recovery(Recovery::Forbidden) From 440d1429d60fb38b9d5c0b554303bf1ddaa107fe Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Sat, 1 Oct 2022 11:56:17 -0400 Subject: [PATCH 03/10] Increase visibility of `expr::choose_separator_tactic` --- src/expr.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/expr.rs b/src/expr.rs index 348cffc21ee..a85c617f9b2 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -1446,7 +1446,10 @@ fn rewrite_float_lit( .max_width_error(shape.width, span) } -fn choose_separator_tactic(context: &RewriteContext<'_>, span: Span) -> Option { +pub(crate) fn choose_separator_tactic( + context: &RewriteContext<'_>, + span: Span, +) -> Option { if context.inside_macro() { if span_ends_with_comma(context, span) { Some(SeparatorTactic::Always) From 0bbf005b0b7ff32bf87c0ad552dac77fec9cc532 Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Sat, 1 Oct 2022 02:59:43 -0400 Subject: [PATCH 04/10] Better encapsulate guard rewrite logic in relation to pattern rewirte The guard rewrite rules vary based on how the pattern was rewritten. That logic was previously written in `rewrite_match_arm`, but now it's fully encapsulates in `rewrite_guard`. --- src/matches.rs | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/matches.rs b/src/matches.rs index 1727a9de868..5c833fb3bfd 100644 --- a/src/matches.rs +++ b/src/matches.rs @@ -296,15 +296,7 @@ fn rewrite_match_arm( let pats_str = arm.pat.rewrite_result(context, pat_shape)?; // Guard - let block_like_pat = trimmed_last_line_width(&pats_str) <= context.config.tab_spaces(); - let new_line_guard = pats_str.contains('\n') && !block_like_pat; - let guard_str = rewrite_guard( - context, - &arm.guard, - shape, - trimmed_last_line_width(&pats_str), - new_line_guard, - )?; + let guard_str = rewrite_guard(context, &arm.guard, shape, &pats_str)?; let lhs_str = combine_strs_with_missing_comments( context, @@ -568,8 +560,26 @@ fn rewrite_match_body( } } +pub(crate) fn rewrite_guard( + context: &RewriteContext<'_>, + guard: &Option>, + shape: Shape, + pattern_str: &str, +) -> Option { + let last_line_pattern_width = trimmed_last_line_width(pattern_str); + let block_like_pat = last_line_pattern_width <= context.config.tab_spaces(); + let new_line_guard = pattern_str.contains('\n') && !block_like_pat; + rewrite_guard_inner( + context, + guard, + shape, + last_line_pattern_width, + new_line_guard, + ) +} + // The `if ...` guard on a match arm. -fn rewrite_guard( +fn rewrite_guard_inner( context: &RewriteContext<'_>, guard: &Option>, shape: Shape, From 1979324e3b1e0bdb329e6f7e40fd0c8c58b2e12a Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Sat, 1 Oct 2022 11:58:06 -0400 Subject: [PATCH 05/10] Add MatchesMacroItem and imp Rewrite, Spanned, and IntoOverflowableItem These traits will make it easier to implement rewriting matches! in terms of `overflow::rewrite_with_*` methods. --- src/macros.rs | 23 +++++++++++++++++++++++ src/matches.rs | 2 +- src/overflow.rs | 13 +++++++++++-- src/parse/macros/matches.rs | 26 ++++++++++++++++++++------ src/spanned.rs | 11 +++++++++++ 5 files changed, 66 insertions(+), 9 deletions(-) diff --git a/src/macros.rs b/src/macros.rs index 16897e57dcb..2cf5387be81 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -26,8 +26,10 @@ use crate::config::StyleEdition; use crate::config::lists::*; use crate::expr::{RhsAssignKind, rewrite_array, rewrite_assign_rhs}; use crate::lists::{ListFormatting, itemize_list, write_list}; +use crate::matches::rewrite_guard; use crate::overflow; use crate::parse::macros::lazy_static::parse_lazy_static; +use crate::parse::macros::matches::MatchesMacroItem; use crate::parse::macros::{ParsedMacroArgs, parse_expr, parse_macro_args}; use crate::rewrite::{ MacroErrorKind, Rewrite, RewriteContext, RewriteError, RewriteErrorExt, RewriteResult, @@ -1394,6 +1396,27 @@ impl MacroBranch { } } +impl Rewrite for MatchesMacroItem { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + self.rewrite_result(context, shape).ok() + } + + fn rewrite_result( + &self, + context: &RewriteContext<'_>, + shape: crate::shape::Shape, + ) -> RewriteResult { + match self { + Self::Expr(expr) => expr.rewrite_result(context, shape), + Self::Arm(pat, guard) => { + let pats_str = pat.rewrite_result(context, shape)?; + let guard_str = rewrite_guard(context, guard, shape, &pats_str)?; + Ok(pats_str + &guard_str) + } + } + } +} + /// Format `lazy_static!` from . /// /// # Expected syntax diff --git a/src/matches.rs b/src/matches.rs index 5c833fb3bfd..4a0229e7abb 100644 --- a/src/matches.rs +++ b/src/matches.rs @@ -565,7 +565,7 @@ pub(crate) fn rewrite_guard( guard: &Option>, shape: Shape, pattern_str: &str, -) -> Option { +) -> RewriteResult { let last_line_pattern_width = trimmed_last_line_width(pattern_str); let block_like_pat = last_line_pattern_width <= context.config.tab_spaces(); let new_line_guard = pattern_str.contains('\n') && !block_like_pat; diff --git a/src/overflow.rs b/src/overflow.rs index 19f7b06f8a3..7e9d6ce407d 100644 --- a/src/overflow.rs +++ b/src/overflow.rs @@ -19,6 +19,7 @@ use crate::lists::{ ListFormatting, ListItem, Separator, definitive_tactic, itemize_list, write_list, }; use crate::macros::MacroArg; +use crate::parse::macros::matches::MatchesMacroItem; use crate::patterns::{TuplePatField, can_be_overflowed_pat}; use crate::rewrite::{Rewrite, RewriteContext, RewriteError, RewriteErrorExt, RewriteResult}; use crate::shape::Shape; @@ -85,6 +86,7 @@ pub(crate) enum OverflowableItem<'a> { Ty(&'a ast::Ty), Pat(&'a ast::Pat), PreciseCapturingArg(&'a ast::PreciseCapturingArg), + MatchesMacroItem(&'a MatchesMacroItem), } impl<'a> Rewrite for OverflowableItem<'a> { @@ -130,6 +132,7 @@ impl<'a> OverflowableItem<'a> { OverflowableItem::Ty(ty) => f(*ty), OverflowableItem::Pat(pat) => f(*pat), OverflowableItem::PreciseCapturingArg(arg) => f(*arg), + OverflowableItem::MatchesMacroItem(item) => f(*item), } } @@ -154,7 +157,9 @@ impl<'a> OverflowableItem<'a> { pub(crate) fn is_expr(&self) -> bool { matches!( self, - OverflowableItem::Expr(..) | OverflowableItem::MacroArg(MacroArg::Expr(..)) + OverflowableItem::Expr(..) + | OverflowableItem::MacroArg(MacroArg::Expr(..)) + | OverflowableItem::MatchesMacroItem(MatchesMacroItem::Expr(..)) ) } @@ -170,6 +175,7 @@ impl<'a> OverflowableItem<'a> { match self { OverflowableItem::Expr(expr) => Some(expr), OverflowableItem::MacroArg(MacroArg::Expr(ref expr)) => Some(expr), + OverflowableItem::MatchesMacroItem(MatchesMacroItem::Expr(expr)) => Some(expr), _ => None, } } @@ -265,7 +271,10 @@ impl_into_overflowable_item_for_ast_node!( Pat, PreciseCapturingArg ); -impl_into_overflowable_item_for_rustfmt_types!([MacroArg], [SegmentParam, TuplePatField]); +impl_into_overflowable_item_for_rustfmt_types!( + [MacroArg, MatchesMacroItem], + [SegmentParam, TuplePatField] +); pub(crate) fn into_overflowable_list<'a, T>( iter: impl Iterator, diff --git a/src/parse/macros/matches.rs b/src/parse/macros/matches.rs index f2d05069d20..4bb61212524 100644 --- a/src/parse/macros/matches.rs +++ b/src/parse/macros/matches.rs @@ -1,9 +1,8 @@ use rustc_ast::ast; use rustc_ast::ptr::P; -use rustc_ast::token::TokenKind; use rustc_ast::tokenstream::TokenStream; +use rustc_parse::exp; use rustc_parse::parser::{CommaRecoveryMode, RecoverColon, RecoverComma}; -use rustc_span::symbol::kw; use super::is_token_tree_comma; use crate::rewrite::RewriteContext; @@ -17,7 +16,7 @@ pub(crate) struct Matches { /// Parse matches! from pub(crate) fn parse_matches(context: &RewriteContext<'_>, ts: TokenStream) -> Option { - let mut cursor = ts.trees().peekable(); + let mut cursor = ts.iter().peekable(); // remove trailing commmas from the TokenStream since they lead to errors when parsing ast::Pat // using parse_pat_allow_top_alt below since the parser isn't expecting a trailing comma. // This is only an issue when the `ast::Pat` is not followed by a guard. In either case it's ok @@ -34,10 +33,10 @@ pub(crate) fn parse_matches(context: &RewriteContext<'_>, ts: TokenStream) -> Op let mut parser = super::build_parser(context, ts); let expr = parser.parse_expr().ok()?; - parser.eat(&TokenKind::Comma); + let _ = parser.eat(exp!(Comma)); let pat = parser - .parse_pat_allow_top_alt( + .parse_pat_allow_top_guard( None, RecoverComma::Yes, RecoverColon::Yes, @@ -45,10 +44,25 @@ pub(crate) fn parse_matches(context: &RewriteContext<'_>, ts: TokenStream) -> Op ) .ok()?; - let guard = if parser.eat_keyword(kw::If) { + let guard = if parser.eat_keyword(exp!(If)) { Some(parser.parse_expr().ok()?) } else { None }; Some(Matches { expr, pat, guard }) } + +impl Matches { + pub(crate) fn items(self) -> [MatchesMacroItem; 2] { + [ + MatchesMacroItem::Expr(self.expr), + MatchesMacroItem::Arm(self.pat, self.guard), + ] + } +} + +#[derive(Debug)] +pub(crate) enum MatchesMacroItem { + Expr(P), + Arm(P, Option>), +} diff --git a/src/spanned.rs b/src/spanned.rs index 507647566d4..3d084c89f7d 100644 --- a/src/spanned.rs +++ b/src/spanned.rs @@ -4,6 +4,7 @@ use rustc_ast::{ast, ptr}; use rustc_span::{Span, source_map}; use crate::macros::MacroArg; +use crate::parse::macros::matches::MatchesMacroItem; use crate::patterns::RangeOperand; use crate::utils::{mk_sp, outer_attributes}; @@ -211,3 +212,13 @@ impl<'a, T> Spanned for RangeOperand<'a, T> { self.span } } + +impl Spanned for MatchesMacroItem { + fn span(&self) -> rustc_span::Span { + match self { + Self::Expr(expr) => expr.span, + Self::Arm(pat, None) => pat.span, + Self::Arm(pat, Some(guard)) => mk_sp(pat.span.lo(), guard.span.hi()), + } + } +} From 208a70b082dcc95da786562f794512288fe1d9d4 Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Fri, 30 Sep 2022 18:19:57 -0400 Subject: [PATCH 06/10] Special case `matches!` macro Now that we're able to parse the TokenStream of `matches!` calls into a `Matches` struct and then convert that `Matches` struct into an iterable which implments `IntoOverflowableItem` we're able to implement `matches!` rewriting in terms of `overflow::rewrite_with_*` calls. --- src/macros.rs | 50 +++++++++++++++++++++++++++++++++++-- src/parse/macros/matches.rs | 15 ++++++----- 2 files changed, 57 insertions(+), 8 deletions(-) diff --git a/src/macros.rs b/src/macros.rs index 2cf5387be81..7a1e15e026a 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -24,12 +24,12 @@ use crate::comment::{ }; use crate::config::StyleEdition; use crate::config::lists::*; -use crate::expr::{RhsAssignKind, rewrite_array, rewrite_assign_rhs}; +use crate::expr::{RhsAssignKind, choose_separator_tactic, rewrite_array, rewrite_assign_rhs}; use crate::lists::{ListFormatting, itemize_list, write_list}; use crate::matches::rewrite_guard; use crate::overflow; use crate::parse::macros::lazy_static::parse_lazy_static; -use crate::parse::macros::matches::MatchesMacroItem; +use crate::parse::macros::matches::{MatchesMacroItem, parse_matches}; use crate::parse::macros::{ParsedMacroArgs, parse_expr, parse_macro_args}; use crate::rewrite::{ MacroErrorKind, Rewrite, RewriteContext, RewriteError, RewriteErrorExt, RewriteResult, @@ -240,6 +240,12 @@ fn rewrite_macro_inner( _ => return Err(err), }, } + } else if macro_name == "matches!" + && context.config.style_edition() >= StyleEdition::Edition2027 + { + if let success @ Ok(..) = format_matches(context, shape, ¯o_name, mac) { + return success; + } } let ParsedMacroArgs { @@ -1417,6 +1423,46 @@ impl Rewrite for MatchesMacroItem { } } +/// Format `matches!` from +/// +/// # Expected syntax +/// +/// ```text +/// matches!(expr, pat) +/// matches!(expr, pat if expr) +/// ``` +fn format_matches( + context: &RewriteContext<'_>, + shape: Shape, + name: &str, + mac: &ast::MacCall, +) -> RewriteResult { + let span = mac.span(); + let matches = parse_matches(context, mac.args.tokens.clone())?.items(); + let force_separator_tactic = choose_separator_tactic(context, span); + match mac.args.delim { + Delimiter::Parenthesis => overflow::rewrite_with_parens( + context, + name, + matches.iter(), + shape, + span, + shape.width, + force_separator_tactic, + ), + Delimiter::Bracket => overflow::rewrite_with_square_brackets( + context, + name, + matches.iter(), + shape, + span, + force_separator_tactic, + None, + ), + Delimiter::Brace | Delimiter::Invisible(_) => Err(RewriteError::Unknown), + } +} + /// Format `lazy_static!` from . /// /// # Expected syntax diff --git a/src/parse/macros/matches.rs b/src/parse/macros/matches.rs index 4bb61212524..2b6a6d5dc9d 100644 --- a/src/parse/macros/matches.rs +++ b/src/parse/macros/matches.rs @@ -5,7 +5,7 @@ use rustc_parse::exp; use rustc_parse::parser::{CommaRecoveryMode, RecoverColon, RecoverComma}; use super::is_token_tree_comma; -use crate::rewrite::RewriteContext; +use crate::rewrite::{RewriteContext, RewriteError}; #[derive(Debug)] pub(crate) struct Matches { @@ -15,7 +15,10 @@ pub(crate) struct Matches { } /// Parse matches! from -pub(crate) fn parse_matches(context: &RewriteContext<'_>, ts: TokenStream) -> Option { +pub(crate) fn parse_matches( + context: &RewriteContext<'_>, + ts: TokenStream, +) -> Result { let mut cursor = ts.iter().peekable(); // remove trailing commmas from the TokenStream since they lead to errors when parsing ast::Pat // using parse_pat_allow_top_alt below since the parser isn't expecting a trailing comma. @@ -31,7 +34,7 @@ pub(crate) fn parse_matches(context: &RewriteContext<'_>, ts: TokenStream) -> Op let ts = token_trees.into_iter().collect(); let mut parser = super::build_parser(context, ts); - let expr = parser.parse_expr().ok()?; + let expr = parser.parse_expr().map_err(|_| RewriteError::Unknown)?; let _ = parser.eat(exp!(Comma)); @@ -42,14 +45,14 @@ pub(crate) fn parse_matches(context: &RewriteContext<'_>, ts: TokenStream) -> Op RecoverColon::Yes, CommaRecoveryMode::EitherTupleOrPipe, ) - .ok()?; + .map_err(|_| RewriteError::Unknown)?; let guard = if parser.eat_keyword(exp!(If)) { - Some(parser.parse_expr().ok()?) + Some(parser.parse_expr().map_err(|_| RewriteError::Unknown)?) } else { None }; - Some(Matches { expr, pat, guard }) + Ok(Matches { expr, pat, guard }) } impl Matches { From b67ce2f048ec19349c0c8c6d4e7cc0263a80b142 Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Sat, 1 Oct 2022 12:46:22 -0400 Subject: [PATCH 07/10] Add unit tests for `matches!` special case formatting The unit tests cover version One and Two formatting --- tests/source/macros/matches/version_one.rs | 134 ++++++++++++++++ tests/source/macros/matches/version_two.rs | 134 ++++++++++++++++ tests/target/macros/matches/version_one.rs | 172 +++++++++++++++++++++ tests/target/macros/matches/version_two.rs | 154 ++++++++++++++++++ 4 files changed, 594 insertions(+) create mode 100644 tests/source/macros/matches/version_one.rs create mode 100644 tests/source/macros/matches/version_two.rs create mode 100644 tests/target/macros/matches/version_one.rs create mode 100644 tests/target/macros/matches/version_two.rs diff --git a/tests/source/macros/matches/version_one.rs b/tests/source/macros/matches/version_one.rs new file mode 100644 index 00000000000..2fa2fafbdf4 --- /dev/null +++ b/tests/source/macros/matches/version_one.rs @@ -0,0 +1,134 @@ +//rustfmt-version: One + +// To visually verify the special case handling of `matches!` we include the equivalent match expr + +// issue #4462 +fn issue_4462() { +matches!(c, + 'x' | 'c' | 'b' | 'B' | '?' | 'h' | 'H' | 'i' | 'I' | 'l' | 'L' | 'q' | 'Q' | 'n' + | 'N' | 'f' | 'd' | 's' | 'p' | 'P'); + +match c { + 'x' | 'c' | 'b' | 'B' | '?' | 'h' | 'H' | 'i' | 'I' | 'l' | 'L' | 'q' | 'Q' | 'n' + | 'N' | 'f' | 'd' | 's' | 'p' | 'P' => {} +} +} + +// issue #4885 +fn issue_4885() { +matches!( + c, + '\\' | '.' | '+' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | + '$' | '#' | '&' | '-' | '~' | '*' | '?' +); + +match c { + '\\' | '.' | '+' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | + '$' | '#' | '&' | '-' | '~' | '*' | '?' => {} +} +} + +// issue #5176 +fn issue_5176() { +matches!(self, | Self::A | Self::B); +match self { + | Self::A | Self::B => {} +} +} + +// issue #5547 +fn issue_5547() { + matches!(something.very_very_very.long.even.more.fields, Some(very_long_field_name_name_to_check) if method(very_long_field_name)); + match something.very_very_very.long.even.more.fields { + Some(very_long_field_name_name_to_check) if method(very_long_field_name) => {} + } +} + +// other scenarios +fn other_scenarios() { + + // no guard with trailing comma + matches!(self, | Self::A | Self::B,); + match self { + | Self::A | Self::B => {} + } + + // guard with trailing comma + matches!(something.very_very_very.long.even.more.fields, Some(very_long_field_name_name_to_check) if method(very_long_field_name),); + match something.very_very_very.long.even.more.fields { + Some(very_long_field_name_name_to_check) if method(very_long_field_name) => {} + } + + // short expr and pattern, but guard is long. + matches!(something, Some(_) if method(very_long_input_1, very_long_input_2, very_long_input_3, very_long_input_4, very_long_input_5),); + match something { + Some(_) if method(very_long_input_1, very_long_input_2, very_long_input_3, very_long_input_4, very_long_input_5) => {} + } + + // square brackets + matches![self, | Self::A | Self::B]; + match self { + | Self::A | Self::B => {} + } + // curly braces + matches!{self, | Self::A | Self::B}; + match self { + | Self::A | Self::B => {} + } +} + +// nested matches! calls +impl Mystruct { + pub(crate) fn is_expr(&self) -> bool { + matches!( + self, + OverflowableItem::Expr(..) + | OverflowableItem::MacroArg(MacroArg::Expr(..)) + | OverflowableItem::MatchMacroItem(MatchMacroItem::Expr(..)) + ); + + match self { + OverflowableItem::Expr(..) + | OverflowableItem::MacroArg(MacroArg::Expr(..)) + | OverflowableItem::MatchMacroItem(MatchMacroItem::Expr(..)) => {} + } + } + + pub(crate) fn is_expr_with_guard(&self) -> bool { + matches!( + self, + OverflowableItem::Expr(..) + | OverflowableItem::MacroArg(MacroArg::Expr(..)) + | OverflowableItem::MatchMacroItem(MatchMacroItem::Expr(..)) + if self.condition() + ); + + match self { + OverflowableItem::Expr(..) + | OverflowableItem::MacroArg(MacroArg::Expr(..)) + | OverflowableItem::MatchMacroItem(MatchMacroItem::Expr(..)) + if self.condition() => {} + } + } +} + +fn multi_line_struct_pattern_with_guard() { + matches!( + token, + Token::Dimension { + value, + ref unit, + .. + } if num_context.is_ok(context.parsing_mode, value) + ); + + match token { + Token::Dimension { + value, + ref unit, + .. + } if num_context.is_ok(context.parsing_mode, value) => { + // body + }, + } +} diff --git a/tests/source/macros/matches/version_two.rs b/tests/source/macros/matches/version_two.rs new file mode 100644 index 00000000000..4cd0aee7401 --- /dev/null +++ b/tests/source/macros/matches/version_two.rs @@ -0,0 +1,134 @@ +//rustfmt-version: Two + +// To visually verify the special case handling of `matches!` we include the equivalent match expr + +// issue #4462 +fn issue_4462() { +matches!(c, + 'x' | 'c' | 'b' | 'B' | '?' | 'h' | 'H' | 'i' | 'I' | 'l' | 'L' | 'q' | 'Q' | 'n' + | 'N' | 'f' | 'd' | 's' | 'p' | 'P'); + +match c { + 'x' | 'c' | 'b' | 'B' | '?' | 'h' | 'H' | 'i' | 'I' | 'l' | 'L' | 'q' | 'Q' | 'n' + | 'N' | 'f' | 'd' | 's' | 'p' | 'P' => {} +} +} + +// issue #4885 +fn issue_4885() { +matches!( + c, + '\\' | '.' | '+' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | + '$' | '#' | '&' | '-' | '~' | '*' | '?' +); + +match c { + '\\' | '.' | '+' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | + '$' | '#' | '&' | '-' | '~' | '*' | '?' => {} +} +} + +// issue #5176 +fn issue_5176() { +matches!(self, | Self::A | Self::B); +match self { + | Self::A | Self::B => {} +} +} + +// issue #5547 +fn issue_5547() { + matches!(something.very_very_very.long.even.more.fields, Some(very_long_field_name_name_to_check) if method(very_long_field_name)); + match something.very_very_very.long.even.more.fields { + Some(very_long_field_name_name_to_check) if method(very_long_field_name) => {} + } +} + +// other scenarios +fn other_scenarios() { + + // no guard with trailing comma + matches!(self, | Self::A | Self::B,); + match self { + | Self::A | Self::B => {} + } + + // guard with trailing comma + matches!(something.very_very_very.long.even.more.fields, Some(very_long_field_name_name_to_check) if method(very_long_field_name),); + match something.very_very_very.long.even.more.fields { + Some(very_long_field_name_name_to_check) if method(very_long_field_name) => {} + } + + // short expr and pattern, but guard is long. + matches!(something, Some(_) if method(very_long_input_1, very_long_input_2, very_long_input_3, very_long_input_4, very_long_input_5),); + match something { + Some(_) if method(very_long_input_1, very_long_input_2, very_long_input_3, very_long_input_4, very_long_input_5) => {} + } + + // square brackets + matches![self, | Self::A | Self::B]; + match self { + | Self::A | Self::B => {} + } + // curly braces + matches!{self, | Self::A | Self::B}; + match self { + | Self::A | Self::B => {} + } +} + +// nested matches! calls +impl Mystruct { + pub(crate) fn is_expr(&self) -> bool { + matches!( + self, + OverflowableItem::Expr(..) + | OverflowableItem::MacroArg(MacroArg::Expr(..)) + | OverflowableItem::MatchMacroItem(MatchMacroItem::Expr(..)) + ); + + match self { + OverflowableItem::Expr(..) + | OverflowableItem::MacroArg(MacroArg::Expr(..)) + | OverflowableItem::MatchMacroItem(MatchMacroItem::Expr(..)) => {} + } + } + + pub(crate) fn is_expr_with_guard(&self) -> bool { + matches!( + self, + OverflowableItem::Expr(..) + | OverflowableItem::MacroArg(MacroArg::Expr(..)) + | OverflowableItem::MatchMacroItem(MatchMacroItem::Expr(..)) + if self.condition() + ); + + match self { + OverflowableItem::Expr(..) + | OverflowableItem::MacroArg(MacroArg::Expr(..)) + | OverflowableItem::MatchMacroItem(MatchMacroItem::Expr(..)) + if self.condition() => {} + } + } +} + +fn multi_line_struct_pattern_with_guard() { + matches!( + token, + Token::Dimension { + value, + ref unit, + .. + } if num_context.is_ok(context.parsing_mode, value) + ); + + match token { + Token::Dimension { + value, + ref unit, + .. + } if num_context.is_ok(context.parsing_mode, value) => { + // body + }, + } +} diff --git a/tests/target/macros/matches/version_one.rs b/tests/target/macros/matches/version_one.rs new file mode 100644 index 00000000000..b3c24eff62f --- /dev/null +++ b/tests/target/macros/matches/version_one.rs @@ -0,0 +1,172 @@ +//rustfmt-version: One + +// To visually verify the special case handling of `matches!` we include the equivalent match expr + +// issue #4462 +fn issue_4462() { + matches!( + c, + 'x' | 'c' + | 'b' + | 'B' + | '?' + | 'h' + | 'H' + | 'i' + | 'I' + | 'l' + | 'L' + | 'q' + | 'Q' + | 'n' + | 'N' + | 'f' + | 'd' + | 's' + | 'p' + | 'P' + ); + + match c { + 'x' | 'c' | 'b' | 'B' | '?' | 'h' | 'H' | 'i' | 'I' | 'l' | 'L' | 'q' | 'Q' | 'n' | 'N' + | 'f' | 'd' | 's' | 'p' | 'P' => {} + } +} + +// issue #4885 +fn issue_4885() { + matches!( + c, + '\\' | '.' + | '+' + | '(' + | ')' + | '|' + | '[' + | ']' + | '{' + | '}' + | '^' + | '$' + | '#' + | '&' + | '-' + | '~' + | '*' + | '?' + ); + + match c { + '\\' | '.' | '+' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$' | '#' | '&' + | '-' | '~' | '*' | '?' => {} + } +} + +// issue #5176 +fn issue_5176() { + matches!(self, |Self::A| Self::B); + match self { + Self::A | Self::B => {} + } +} + +// issue #5547 +fn issue_5547() { + matches!(something.very_very_very.long.even.more.fields, Some(very_long_field_name_name_to_check) if method(very_long_field_name)); + match something.very_very_very.long.even.more.fields { + Some(very_long_field_name_name_to_check) if method(very_long_field_name) => {} + } +} + +// other scenarios +fn other_scenarios() { + // no guard with trailing comma + matches!(self, |Self::A| Self::B,); + match self { + Self::A | Self::B => {} + } + + // guard with trailing comma + matches!(something.very_very_very.long.even.more.fields, Some(very_long_field_name_name_to_check) if method(very_long_field_name),); + match something.very_very_very.long.even.more.fields { + Some(very_long_field_name_name_to_check) if method(very_long_field_name) => {} + } + + // short expr and pattern, but guard is long. + matches!(something, Some(_) if method(very_long_input_1, very_long_input_2, very_long_input_3, very_long_input_4, very_long_input_5),); + match something { + Some(_) + if method( + very_long_input_1, + very_long_input_2, + very_long_input_3, + very_long_input_4, + very_long_input_5, + ) => {} + } + + // square brackets + matches![self, |Self::A| Self::B]; + match self { + Self::A | Self::B => {} + } + // curly braces + matches! {self, | Self::A | Self::B}; + match self { + Self::A | Self::B => {} + } +} + +// nested matches! calls +impl Mystruct { + pub(crate) fn is_expr(&self) -> bool { + matches!( + self, + OverflowableItem::Expr(..) + | OverflowableItem::MacroArg(MacroArg::Expr(..)) + | OverflowableItem::MatchMacroItem(MatchMacroItem::Expr(..)) + ); + + match self { + OverflowableItem::Expr(..) + | OverflowableItem::MacroArg(MacroArg::Expr(..)) + | OverflowableItem::MatchMacroItem(MatchMacroItem::Expr(..)) => {} + } + } + + pub(crate) fn is_expr_with_guard(&self) -> bool { + matches!( + self, + OverflowableItem::Expr(..) + | OverflowableItem::MacroArg(MacroArg::Expr(..)) + | OverflowableItem::MatchMacroItem(MatchMacroItem::Expr(..)) + if self.condition() + ); + + match self { + OverflowableItem::Expr(..) + | OverflowableItem::MacroArg(MacroArg::Expr(..)) + | OverflowableItem::MatchMacroItem(MatchMacroItem::Expr(..)) + if self.condition() => {} + } + } +} + +fn multi_line_struct_pattern_with_guard() { + matches!( + token, + Token::Dimension { + value, + ref unit, + .. + } if num_context.is_ok(context.parsing_mode, value) + ); + + match token { + Token::Dimension { + value, ref unit, .. + } if num_context.is_ok(context.parsing_mode, value) => { + // body + } + } +} diff --git a/tests/target/macros/matches/version_two.rs b/tests/target/macros/matches/version_two.rs new file mode 100644 index 00000000000..3137c0f42ca --- /dev/null +++ b/tests/target/macros/matches/version_two.rs @@ -0,0 +1,154 @@ +//rustfmt-version: Two + +// To visually verify the special case handling of `matches!` we include the equivalent match expr + +// issue #4462 +fn issue_4462() { + matches!( + c, + 'x' | 'c' | 'b' | 'B' | '?' | 'h' | 'H' | 'i' | 'I' | 'l' | 'L' | 'q' | 'Q' | 'n' | 'N' + | 'f' | 'd' | 's' | 'p' | 'P' + ); + + match c { + 'x' | 'c' | 'b' | 'B' | '?' | 'h' | 'H' | 'i' | 'I' | 'l' | 'L' | 'q' | 'Q' | 'n' | 'N' + | 'f' | 'd' | 's' | 'p' | 'P' => {} + } +} + +// issue #4885 +fn issue_4885() { + matches!( + c, + '\\' | '.' | '+' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$' | '#' | '&' | '-' + | '~' | '*' | '?' + ); + + match c { + '\\' | '.' | '+' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$' | '#' | '&' + | '-' | '~' | '*' | '?' => {} + } +} + +// issue #5176 +fn issue_5176() { + matches!(self, Self::A | Self::B); + match self { + Self::A | Self::B => {} + } +} + +// issue #5547 +fn issue_5547() { + matches!( + something.very_very_very.long.even.more.fields, + Some(very_long_field_name_name_to_check) if method(very_long_field_name) + ); + match something.very_very_very.long.even.more.fields { + Some(very_long_field_name_name_to_check) if method(very_long_field_name) => {} + } +} + +// other scenarios +fn other_scenarios() { + // no guard with trailing comma + matches!(self, Self::A | Self::B,); + match self { + Self::A | Self::B => {} + } + + // guard with trailing comma + matches!( + something.very_very_very.long.even.more.fields, + Some(very_long_field_name_name_to_check) if method(very_long_field_name), + ); + match something.very_very_very.long.even.more.fields { + Some(very_long_field_name_name_to_check) if method(very_long_field_name) => {} + } + + // short expr and pattern, but guard is long. + matches!( + something, + Some(_) + if method( + very_long_input_1, + very_long_input_2, + very_long_input_3, + very_long_input_4, + very_long_input_5 + ), + ); + match something { + Some(_) + if method( + very_long_input_1, + very_long_input_2, + very_long_input_3, + very_long_input_4, + very_long_input_5, + ) => {} + } + + // square brackets + matches![self, Self::A | Self::B]; + match self { + Self::A | Self::B => {} + } + // curly braces + matches! {self, | Self::A | Self::B}; + match self { + Self::A | Self::B => {} + } +} + +// nested matches! calls +impl Mystruct { + pub(crate) fn is_expr(&self) -> bool { + matches!( + self, + OverflowableItem::Expr(..) + | OverflowableItem::MacroArg(MacroArg::Expr(..)) + | OverflowableItem::MatchMacroItem(MatchMacroItem::Expr(..)) + ); + + match self { + OverflowableItem::Expr(..) + | OverflowableItem::MacroArg(MacroArg::Expr(..)) + | OverflowableItem::MatchMacroItem(MatchMacroItem::Expr(..)) => {} + } + } + + pub(crate) fn is_expr_with_guard(&self) -> bool { + matches!( + self, + OverflowableItem::Expr(..) + | OverflowableItem::MacroArg(MacroArg::Expr(..)) + | OverflowableItem::MatchMacroItem(MatchMacroItem::Expr(..)) + if self.condition() + ); + + match self { + OverflowableItem::Expr(..) + | OverflowableItem::MacroArg(MacroArg::Expr(..)) + | OverflowableItem::MatchMacroItem(MatchMacroItem::Expr(..)) + if self.condition() => {} + } + } +} + +fn multi_line_struct_pattern_with_guard() { + matches!( + token, + Token::Dimension { + value, ref unit, .. + } if num_context.is_ok(context.parsing_mode, value) + ); + + match token { + Token::Dimension { + value, ref unit, .. + } if num_context.is_ok(context.parsing_mode, value) => { + // body + } + } +} From 281802a52072de8e8b0371f5d31300fbbca08da8 Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Thu, 9 Mar 2023 09:03:13 -0500 Subject: [PATCH 08/10] Add `matches!` test case for issue 5709 --- tests/source/issue_5709.rs | 9 +++++++++ tests/target/issue_5709.rs | 10 ++++++++++ 2 files changed, 19 insertions(+) create mode 100644 tests/source/issue_5709.rs create mode 100644 tests/target/issue_5709.rs diff --git a/tests/source/issue_5709.rs b/tests/source/issue_5709.rs new file mode 100644 index 00000000000..0ae790f2b76 --- /dev/null +++ b/tests/source/issue_5709.rs @@ -0,0 +1,9 @@ +// rustfmt-version: Two + +fn is_something(foo: Foo, bar: Bar) -> bool { + matches!((legacy_required_finality, signature_weight), + | (LegacyRequiredFinality::Any, Insufficient | Weak | Strict) + | (LegacyRequiredFinality::Weak, Weak | Strict) + | (LegacyRequiredFinality::Strict, Strict) + ) +} diff --git a/tests/target/issue_5709.rs b/tests/target/issue_5709.rs new file mode 100644 index 00000000000..ef96af0e6b8 --- /dev/null +++ b/tests/target/issue_5709.rs @@ -0,0 +1,10 @@ +// rustfmt-version: Two + +fn is_something(foo: Foo, bar: Bar) -> bool { + matches!( + (legacy_required_finality, signature_weight), + (LegacyRequiredFinality::Any, Insufficient | Weak | Strict) + | (LegacyRequiredFinality::Weak, Weak | Strict) + | (LegacyRequiredFinality::Strict, Strict) + ) +} From e366a833de221b7cd6b11ff7236dcd78667dc2fc Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Sat, 1 Oct 2022 12:48:17 -0400 Subject: [PATCH 09/10] Apply updated `matches!` formatting to rustfmt Since rustfmt uses version two formatting the self tests were failing due to the new special case handling for `matches!`. various `matches!` calls in the codebase were updated to address this issue. --- tests/source/issue_5709.rs | 2 +- .../{version_two.rs => style_edition_2027.rs} | 2 +- tests/target/issue_5709.rs | 2 +- .../{version_two.rs => style_edition_2027.rs} | 19 +++++++------------ 4 files changed, 10 insertions(+), 15 deletions(-) rename tests/source/macros/matches/{version_two.rs => style_edition_2027.rs} (99%) rename tests/target/macros/matches/{version_two.rs => style_edition_2027.rs} (89%) diff --git a/tests/source/issue_5709.rs b/tests/source/issue_5709.rs index 0ae790f2b76..9624718d5c0 100644 --- a/tests/source/issue_5709.rs +++ b/tests/source/issue_5709.rs @@ -1,4 +1,4 @@ -// rustfmt-version: Two +// rustfmt-style_edition: 2027 fn is_something(foo: Foo, bar: Bar) -> bool { matches!((legacy_required_finality, signature_weight), diff --git a/tests/source/macros/matches/version_two.rs b/tests/source/macros/matches/style_edition_2027.rs similarity index 99% rename from tests/source/macros/matches/version_two.rs rename to tests/source/macros/matches/style_edition_2027.rs index 4cd0aee7401..d0e2d8bfa38 100644 --- a/tests/source/macros/matches/version_two.rs +++ b/tests/source/macros/matches/style_edition_2027.rs @@ -1,4 +1,4 @@ -//rustfmt-version: Two +//rustfmt-style_edition: 2027 // To visually verify the special case handling of `matches!` we include the equivalent match expr diff --git a/tests/target/issue_5709.rs b/tests/target/issue_5709.rs index ef96af0e6b8..dde3558fa02 100644 --- a/tests/target/issue_5709.rs +++ b/tests/target/issue_5709.rs @@ -1,4 +1,4 @@ -// rustfmt-version: Two +// rustfmt-style_edition: 2027 fn is_something(foo: Foo, bar: Bar) -> bool { matches!( diff --git a/tests/target/macros/matches/version_two.rs b/tests/target/macros/matches/style_edition_2027.rs similarity index 89% rename from tests/target/macros/matches/version_two.rs rename to tests/target/macros/matches/style_edition_2027.rs index 3137c0f42ca..c478e9bb242 100644 --- a/tests/target/macros/matches/version_two.rs +++ b/tests/target/macros/matches/style_edition_2027.rs @@ -1,4 +1,4 @@ -//rustfmt-version: Two +//rustfmt-style_edition: 2027 // To visually verify the special case handling of `matches!` we include the equivalent match expr @@ -69,14 +69,7 @@ fn other_scenarios() { // short expr and pattern, but guard is long. matches!( something, - Some(_) - if method( - very_long_input_1, - very_long_input_2, - very_long_input_3, - very_long_input_4, - very_long_input_5 - ), + Some(_) if method(very_long_input_1, very_long_input_2, very_long_input_3, very_long_input_4, very_long_input_5), ); match something { Some(_) @@ -122,8 +115,8 @@ impl Mystruct { matches!( self, OverflowableItem::Expr(..) - | OverflowableItem::MacroArg(MacroArg::Expr(..)) - | OverflowableItem::MatchMacroItem(MatchMacroItem::Expr(..)) + | OverflowableItem::MacroArg(MacroArg::Expr(..)) + | OverflowableItem::MatchMacroItem(MatchMacroItem::Expr(..)) if self.condition() ); @@ -140,7 +133,9 @@ fn multi_line_struct_pattern_with_guard() { matches!( token, Token::Dimension { - value, ref unit, .. + value, + ref unit, + .. } if num_context.is_ok(context.parsing_mode, value) ); From 92afb9d1f901bdea26c3d9ca99e51b9a4f0bc669 Mon Sep 17 00:00:00 2001 From: Yacin Tmimi Date: Sat, 30 Aug 2025 23:14:48 -0400 Subject: [PATCH 10/10] Add test case for issue 6650 --- tests/source/issue_6650.rs | 5 +++++ tests/target/issue_6650.rs | 15 +++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 tests/source/issue_6650.rs create mode 100644 tests/target/issue_6650.rs diff --git a/tests/source/issue_6650.rs b/tests/source/issue_6650.rs new file mode 100644 index 00000000000..72f29749aef --- /dev/null +++ b/tests/source/issue_6650.rs @@ -0,0 +1,5 @@ +// rustfmt-style_edition: 2027 + +fn main() { + matches!(stmt, ast::Stmt{kind: ast::StmtKind::MacCall(box ast::MacCallStmt {style : ast::MacStmtStyle::Braces,..}),..}) +} diff --git a/tests/target/issue_6650.rs b/tests/target/issue_6650.rs new file mode 100644 index 00000000000..b05ed62ccee --- /dev/null +++ b/tests/target/issue_6650.rs @@ -0,0 +1,15 @@ +// rustfmt-style_edition: 2027 + +fn main() { + matches!( + stmt, + ast::Stmt { + kind: + ast::StmtKind::MacCall(box ast::MacCallStmt { + style: ast::MacStmtStyle::Braces, + .. + }), + .. + } + ) +}