Skip to content
5 changes: 4 additions & 1 deletion src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1446,7 +1446,10 @@ fn rewrite_float_lit(
.max_width_error(shape.width, span)
}

fn choose_separator_tactic(context: &RewriteContext<'_>, span: Span) -> Option<SeparatorTactic> {
pub(crate) fn choose_separator_tactic(
context: &RewriteContext<'_>,
span: Span,
) -> Option<SeparatorTactic> {
if context.inside_macro() {
if span_ends_with_comma(context, span) {
Some(SeparatorTactic::Always)
Expand Down
71 changes: 70 additions & 1 deletion src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +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, parse_matches};
use crate::parse::macros::{ParsedMacroArgs, parse_expr, parse_macro_args};
use crate::rewrite::{
MacroErrorKind, Rewrite, RewriteContext, RewriteError, RewriteErrorExt, RewriteResult,
Expand Down Expand Up @@ -238,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, &macro_name, mac) {
return success;
}
}

let ParsedMacroArgs {
Expand Down Expand Up @@ -1394,6 +1402,67 @@ impl MacroBranch {
}
}

impl Rewrite for MatchesMacroItem {
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
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 `matches!` from <https://doc.rust-lang.org/std/macro.matches.html>
///
/// # 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 <https://crates.io/crates/lazy_static>.
///
/// # Expected syntax
Expand Down
30 changes: 20 additions & 10 deletions src/matches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -568,8 +560,26 @@ fn rewrite_match_body(
}
}

pub(crate) fn rewrite_guard(
context: &RewriteContext<'_>,
guard: &Option<ptr::P<ast::Expr>>,
shape: Shape,
pattern_str: &str,
) -> 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;
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<ptr::P<ast::Expr>>,
shape: Shape,
Expand Down
13 changes: 11 additions & 2 deletions src/overflow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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> {
Expand Down Expand Up @@ -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),
}
}

Expand All @@ -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(..))
)
}

Expand All @@ -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,
}
}
Expand Down Expand Up @@ -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<Item = &'a T>,
Expand Down
71 changes: 71 additions & 0 deletions src/parse/macros/matches.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use rustc_ast::ast;
use rustc_ast::ptr::P;
use rustc_ast::tokenstream::TokenStream;
use rustc_parse::exp;
use rustc_parse::parser::{CommaRecoveryMode, RecoverColon, RecoverComma};

use super::is_token_tree_comma;
use crate::rewrite::{RewriteContext, RewriteError};

#[derive(Debug)]
pub(crate) struct Matches {
pub(crate) expr: P<ast::Expr>,
pub(crate) pat: P<ast::Pat>,
pub(crate) guard: Option<P<ast::Expr>>,
}

/// Parse matches! from <https://doc.rust-lang.org/std/macro.matches.html>
pub(crate) fn parse_matches(
context: &RewriteContext<'_>,
ts: TokenStream,
) -> Result<Matches, RewriteError> {
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
// 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().map_err(|_| RewriteError::Unknown)?;

let _ = parser.eat(exp!(Comma));

let pat = parser
.parse_pat_allow_top_guard(
None,
RecoverComma::Yes,
RecoverColon::Yes,
CommaRecoveryMode::EitherTupleOrPipe,
)
.map_err(|_| RewriteError::Unknown)?;

let guard = if parser.eat_keyword(exp!(If)) {
Some(parser.parse_expr().map_err(|_| RewriteError::Unknown)?)
} else {
None
};
Ok(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<ast::Expr>),
Arm(P<ast::Pat>, Option<P<ast::Expr>>),
}
8 changes: 7 additions & 1 deletion src/parse/macros/mod.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -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)
Expand Down Expand Up @@ -96,6 +97,11 @@ fn check_keyword<'a, 'b: 'a>(parser: &'a mut Parser<'b>) -> Option<MacroArg> {
}
}

/// 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,
Expand Down
11 changes: 11 additions & 0 deletions src/spanned.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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()),
}
}
}
9 changes: 9 additions & 0 deletions tests/source/issue_5709.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// rustfmt-style_edition: 2027

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)
)
}
5 changes: 5 additions & 0 deletions tests/source/issue_6650.rs
Original file line number Diff line number Diff line change
@@ -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,..}),..})
}
Loading
Loading