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

Experimental feature postfix match #121619

Merged
merged 4 commits into from
Mar 22, 2024
Merged
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
11 changes: 10 additions & 1 deletion compiler/rustc_ast/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1436,7 +1436,7 @@ pub enum ExprKind {
/// `'label: loop { block }`
Loop(P<Block>, Option<Label>, Span),
/// A `match` block.
Match(P<Expr>, ThinVec<Arm>),
Match(P<Expr>, ThinVec<Arm>, MatchKind),
/// A closure (e.g., `move |a, b, c| a + b + c`).
Closure(Box<Closure>),
/// A block (`'label: { ... }`).
Expand Down Expand Up @@ -1761,6 +1761,15 @@ pub enum StrStyle {
Raw(u8),
}

/// The kind of match expression
#[derive(Clone, Copy, Encodable, Decodable, Debug, PartialEq)]
pub enum MatchKind {
/// match expr { ... }
Prefix,
/// expr.match { ... }
Postfix,
}

/// A literal in a meta item.
#[derive(Clone, Encodable, Decodable, Debug, HashStable_Generic)]
pub struct MetaItemLit {
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_ast/src/mut_visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1423,7 +1423,7 @@ pub fn noop_visit_expr<T: MutVisitor>(
visit_opt(label, |label| vis.visit_label(label));
vis.visit_span(span);
}
ExprKind::Match(expr, arms) => {
ExprKind::Match(expr, arms, _kind) => {
vis.visit_expr(expr);
arms.flat_map_in_place(|arm| vis.flat_map_arm(arm));
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_ast/src/visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -919,7 +919,7 @@ pub fn walk_expr<'a, V: Visitor<'a>>(visitor: &mut V, expression: &'a Expr) -> V
visit_opt!(visitor, visit_label, opt_label);
try_visit!(visitor.visit_block(block));
}
ExprKind::Match(subexpression, arms) => {
ExprKind::Match(subexpression, arms, _kind) => {
try_visit!(visitor.visit_expr(subexpression));
walk_list!(visitor, visit_arm, arms);
}
Expand Down
7 changes: 5 additions & 2 deletions compiler/rustc_ast_lowering/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,13 @@ impl<'hir> LoweringContext<'_, 'hir> {
)
}),
ExprKind::TryBlock(body) => self.lower_expr_try_block(body),
ExprKind::Match(expr, arms) => hir::ExprKind::Match(
ExprKind::Match(expr, arms, kind) => hir::ExprKind::Match(
self.lower_expr(expr),
self.arena.alloc_from_iter(arms.iter().map(|x| self.lower_arm(x))),
hir::MatchSource::Normal,
match kind {
MatchKind::Prefix => hir::MatchSource::Normal,
MatchKind::Postfix => hir::MatchSource::Postfix,
},
),
ExprKind::Await(expr, await_kw_span) => self.lower_expr_await(*await_kw_span, expr),
ExprKind::Closure(box Closure {
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_ast_passes/src/feature_gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,7 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session, features: &Features) {
gate_all!(generic_const_items, "generic const items are experimental");
gate_all!(unnamed_fields, "unnamed fields are not yet fully implemented");
gate_all!(fn_delegation, "functions delegation is not yet fully implemented");
gate_all!(postfix_match, "postfix match is experimental");

if !visitor.features.never_patterns {
if let Some(spans) = spans.get(&sym::never_patterns) {
Expand Down
20 changes: 15 additions & 5 deletions compiler/rustc_ast_pretty/src/pprust/state/expr.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::pp::Breaks::Inconsistent;
use crate::pprust::state::{AnnNode, PrintState, State, INDENT_UNIT};
use ast::ForLoopKind;
use ast::{ForLoopKind, MatchKind};
use itertools::{Itertools, Position};
use rustc_ast::ptr::P;
use rustc_ast::token;
Expand Down Expand Up @@ -589,12 +589,22 @@ impl<'a> State<'a> {
self.word_nbsp("loop");
self.print_block_with_attrs(blk, attrs);
}
ast::ExprKind::Match(expr, arms) => {
ast::ExprKind::Match(expr, arms, match_kind) => {
self.cbox(0);
self.ibox(0);
self.word_nbsp("match");
self.print_expr_as_cond(expr);
self.space();

match match_kind {
MatchKind::Prefix => {
self.word_nbsp("match");
self.print_expr_as_cond(expr);
self.space();
}
MatchKind::Postfix => {
self.print_expr_as_cond(expr);
self.word_nbsp(".match");
}
}

self.bopen();
self.print_inner_attributes_no_trailing_hardbreak(attrs);
for arm in arms {
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_builtin_macros/src/assert/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ impl<'cx, 'a> Context<'cx, 'a> {
ExprKind::Let(_, local_expr, _, _) => {
self.manage_cond_expr(local_expr);
}
ExprKind::Match(local_expr, _) => {
ExprKind::Match(local_expr, ..) => {
self.manage_cond_expr(local_expr);
}
ExprKind::MethodCall(call) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ fn cs_partial_cmp(
// Reference: https://github.com/rust-lang/rust/pull/103659#issuecomment-1328126354

if !tag_then_data
&& let ExprKind::Match(_, arms) = &mut expr1.kind
&& let ExprKind::Match(_, arms, _) = &mut expr1.kind
&& let Some(last) = arms.last_mut()
&& let PatKind::Wild = last.pat.kind
{
Expand Down
3 changes: 1 addition & 2 deletions compiler/rustc_builtin_macros/src/deriving/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ use crate::deriving::generic::ty::*;
use crate::deriving::generic::*;
use crate::deriving::path_std;

use ast::EnumDef;
use rustc_ast::{self as ast, MetaItem};
use rustc_ast::{self as ast, EnumDef, MetaItem};
use rustc_expand::base::{Annotatable, ExtCtxt};
use rustc_span::symbol::{sym, Ident, Symbol};
use rustc_span::Span;
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_expand/src/build.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::base::ExtCtxt;
use rustc_ast::ptr::P;
use rustc_ast::{self as ast, AttrVec, BlockCheckMode, Expr, LocalKind, PatKind, UnOp};
use rustc_ast::{self as ast, AttrVec, BlockCheckMode, Expr, LocalKind, MatchKind, PatKind, UnOp};
use rustc_ast::{attr, token, util::literal};
use rustc_span::source_map::Spanned;
use rustc_span::symbol::{kw, sym, Ident, Symbol};
Expand Down Expand Up @@ -524,7 +524,7 @@ impl<'a> ExtCtxt<'a> {
}

pub fn expr_match(&self, span: Span, arg: P<ast::Expr>, arms: ThinVec<ast::Arm>) -> P<Expr> {
self.expr(span, ast::ExprKind::Match(arg, arms))
self.expr(span, ast::ExprKind::Match(arg, arms, MatchKind::Prefix))
}

pub fn expr_if(
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_feature/src/unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,8 @@ declare_features! (
(unstable, offset_of_nested, "1.77.0", Some(120140)),
/// Allows using `#[optimize(X)]`.
(unstable, optimize_attribute, "1.34.0", Some(54882)),
/// Allows postfix match `expr.match { ... }`
(unstable, postfix_match, "CURRENT_RUSTC_VERSION", Some(121618)),
/// Allows macro attributes on expressions, statements and non-inline modules.
(unstable, proc_macro_hygiene, "1.30.0", Some(54727)),
/// Allows `&raw const $place_expr` and `&raw mut $place_expr` expressions.
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_hir/src/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2004,6 +2004,8 @@ pub enum LocalSource {
pub enum MatchSource {
/// A `match _ { .. }`.
Normal,
/// A `expr.match { .. }`.
Postfix,
/// A desugared `for _ in _ { .. }` loop.
ForLoopDesugar,
/// A desugared `?` operator.
Expand All @@ -2020,6 +2022,7 @@ impl MatchSource {
use MatchSource::*;
match self {
Normal => "match",
Postfix => ".match",
ForLoopDesugar => "for",
TryDesugar(_) => "?",
AwaitDesugar => ".await",
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_lint/src/unused.rs
Original file line number Diff line number Diff line change
Expand Up @@ -865,7 +865,7 @@ trait UnusedDelimLint {
(iter, UnusedDelimsCtx::ForIterExpr, true, None, Some(body.span.lo()), true)
}

Match(ref head, _) if Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX => {
Match(ref head, ..) if Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX => {
let left = e.span.lo() + rustc_span::BytePos(5);
(head, UnusedDelimsCtx::MatchScrutineeExpr, true, Some(left), None, true)
}
Expand Down Expand Up @@ -1133,7 +1133,7 @@ impl EarlyLintPass for UnusedParens {
}
return;
}
ExprKind::Match(ref _expr, ref arm) => {
ExprKind::Match(ref _expr, ref arm, _) => {
for a in arm {
if let Some(body) = &a.body {
self.check_unused_delims_expr(
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_mir_build/src/thir/pattern/check_match.rs
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,7 @@ impl<'p, 'tcx> MatchVisitor<'p, 'tcx> {
// when the iterator is an uninhabited type. unreachable_code will trigger instead.
hir::MatchSource::ForLoopDesugar if arms.len() == 1 => {}
hir::MatchSource::ForLoopDesugar
| hir::MatchSource::Postfix
| hir::MatchSource::Normal
| hir::MatchSource::FormatArgs => report_arm_reachability(&cx, &report),
// Unreachable patterns in try and await expressions occur when one of
Expand Down
29 changes: 24 additions & 5 deletions compiler/rustc_parse/src/parser/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::errors;
use crate::maybe_recover_from_interpolated_ty_qpath;
use ast::mut_visit::{noop_visit_expr, MutVisitor};
use ast::token::IdentIsRaw;
use ast::{CoroutineKind, ForLoopKind, GenBlockKind, Pat, Path, PathSegment};
use ast::{CoroutineKind, ForLoopKind, GenBlockKind, MatchKind, Pat, Path, PathSegment};
use core::mem;
use rustc_ast::ptr::P;
use rustc_ast::token::{self, Delimiter, Token, TokenKind};
Expand Down Expand Up @@ -1375,6 +1375,13 @@ impl<'a> Parser<'a> {
return Ok(self.mk_await_expr(self_arg, lo));
}

// Post-fix match
if self.eat_keyword(kw::Match) {
let match_span = self.prev_token.span;
self.psess.gated_spans.gate(sym::postfix_match, match_span);
return self.parse_match_block(lo, match_span, self_arg, MatchKind::Postfix);
}

let fn_span_lo = self.token.span;
let mut seg = self.parse_path_segment(PathStyle::Expr, None)?;
self.check_trailing_angle_brackets(&seg, &[&token::OpenDelim(Delimiter::Parenthesis)]);
Expand Down Expand Up @@ -2889,8 +2896,20 @@ impl<'a> Parser<'a> {
/// Parses a `match ... { ... }` expression (`match` token already eaten).
fn parse_expr_match(&mut self) -> PResult<'a, P<Expr>> {
let match_span = self.prev_token.span;
let lo = self.prev_token.span;
let scrutinee = self.parse_expr_res(Restrictions::NO_STRUCT_LITERAL, None)?;

self.parse_match_block(match_span, match_span, scrutinee, MatchKind::Prefix)
}

/// Parses the block of a `match expr { ... }` or a `expr.match { ... }`
/// expression. This is after the match token and scrutinee are eaten
fn parse_match_block(
&mut self,
lo: Span,
match_span: Span,
scrutinee: P<Expr>,
match_kind: MatchKind,
) -> PResult<'a, P<Expr>> {
if let Err(mut e) = self.expect(&token::OpenDelim(Delimiter::Brace)) {
if self.token == token::Semi {
e.span_suggestion_short(
Expand Down Expand Up @@ -2933,15 +2952,15 @@ impl<'a> Parser<'a> {
});
return Ok(self.mk_expr_with_attrs(
span,
ExprKind::Match(scrutinee, arms),
ExprKind::Match(scrutinee, arms, match_kind),
attrs,
));
}
}
}
let hi = self.token.span;
self.bump();
Ok(self.mk_expr_with_attrs(lo.to(hi), ExprKind::Match(scrutinee, arms), attrs))
Ok(self.mk_expr_with_attrs(lo.to(hi), ExprKind::Match(scrutinee, arms, match_kind), attrs))
}

/// Attempt to recover from match arm body with statements and no surrounding braces.
Expand Down Expand Up @@ -3950,7 +3969,7 @@ impl MutVisitor for CondChecker<'_> {
| ExprKind::While(_, _, _)
| ExprKind::ForLoop { .. }
| ExprKind::Loop(_, _, _)
| ExprKind::Match(_, _)
| ExprKind::Match(_, _, _)
| ExprKind::Closure(_)
| ExprKind::Block(_, _)
| ExprKind::Gen(_, _, _)
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_passes/src/check_const.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ impl NonConstExpr {
Self::Match(TryDesugar(_)) => &[sym::const_try],

// All other expressions are allowed.
Self::Loop(Loop | While) | Self::Match(Normal | FormatArgs) => &[],
Self::Loop(Loop | While) | Self::Match(Normal | Postfix | FormatArgs) => &[],
};

Some(gates)
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1319,6 +1319,7 @@ symbols! {
poll,
poll_next,
post_dash_lto: "post-lto",
postfix_match,
powerpc_target_feature,
powf128,
powf16,
Expand Down
22 changes: 22 additions & 0 deletions src/doc/unstable-book/src/language-features/postfix-match.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# `postfix-match`

`postfix-match` adds the feature for matching upon values postfix
the expressions that generate the values.

```rust,edition2021
#![feature(postfix_match)]

enum Foo {
Bar,
Baz
}

fn get_foo() -> Foo {
Foo::Bar
}

get_foo().match {
Foo::Bar => {},
Foo::Baz => panic!(),
}
```
2 changes: 1 addition & 1 deletion src/tools/clippy/clippy_lints/src/redundant_else.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ impl<'ast> Visitor<'ast> for BreakVisitor {
fn visit_expr(&mut self, expr: &'ast Expr) {
self.is_break = match expr.kind {
ExprKind::Break(..) | ExprKind::Continue(..) | ExprKind::Ret(..) => true,
ExprKind::Match(_, ref arms) => arms.iter().all(|arm|
ExprKind::Match(_, ref arms, _) => arms.iter().all(|arm|
arm.body.is_none() || arm.body.as_deref().is_some_and(|body| self.check_expr(body))
),
ExprKind::If(_, ref then, Some(ref els)) => self.check_block(then) && self.check_expr(els),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,7 @@ fn ident_difference_expr_with_base_location(
| (Gen(_, _, _), Gen(_, _, _))
| (Block(_, _), Block(_, _))
| (Closure(_), Closure(_))
| (Match(_, _), Match(_, _))
| (Match(_, _, _), Match(_, _, _))
| (Loop(_, _, _), Loop(_, _, _))
| (ForLoop { .. }, ForLoop { .. })
| (While(_, _, _), While(_, _, _))
Expand Down
2 changes: 1 addition & 1 deletion src/tools/clippy/clippy_utils/src/ast_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ pub fn eq_expr(l: &Expr, r: &Expr) -> bool {
},
(AssignOp(lo, lp, lv), AssignOp(ro, rp, rv)) => lo.node == ro.node && eq_expr(lp, rp) && eq_expr(lv, rv),
(Field(lp, lf), Field(rp, rf)) => eq_id(*lf, *rf) && eq_expr(lp, rp),
(Match(ls, la), Match(rs, ra)) => eq_expr(ls, rs) && over(la, ra, eq_arm),
(Match(ls, la, lkind), Match(rs, ra, rkind)) => (lkind == rkind) && eq_expr(ls, rs) && over(la, ra, eq_arm),
(
Closure(box ast::Closure {
binder: lb,
Expand Down
8 changes: 4 additions & 4 deletions src/tools/rustfmt/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::cmp::min;

use itertools::Itertools;
use rustc_ast::token::{Delimiter, Lit, LitKind};
use rustc_ast::{ast, ptr, token, ForLoopKind};
use rustc_ast::{ast, ptr, token, ForLoopKind, MatchKind};
use rustc_span::{BytePos, Span};

use crate::chains::rewrite_chain;
Expand Down Expand Up @@ -170,8 +170,8 @@ pub(crate) fn format_expr(
}
}
}
ast::ExprKind::Match(ref cond, ref arms) => {
rewrite_match(context, cond, arms, shape, expr.span, &expr.attrs)
ast::ExprKind::Match(ref cond, ref arms, kind) => {
rewrite_match(context, cond, arms, shape, expr.span, &expr.attrs, kind)
Comment on lines +173 to +174
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that the postfix match is an experimental feature I'd prefer not to add formatting support in this PR. For now I think it makes sene to add just enough support so that rustfmt doesn't completely remove the postfix match.

I think the following changes would achieve that.

match kind {
    MatchKind::Prefix => rewrite_match(context, cond, arms, shape, expr.span, &expr.attrs),
    MatchKind::Postfix => {
        // experimental feature postfix match (link to RFC)
        Some(context.snippet(expr.span).to_owned())
    }
}

cc: @calebcartwright

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is what I did originally and iirc it caused rustfmt to crash.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only way I can think for context.snippet(expr.span).to_owned() to crash is if the expr.span isn't valid, but that seems unlikely. You could also try returning None from the MatchKind::Postfix match arm.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually don't have any concerns with the formatting implementation proposed here as this is rather trivial syntactically, a reasonable initial formatting, and in accordance with the style guide policy on nightly-only/experimental syntax.

the more intriguing part for me was the mention of it "crashing", the details of which I'd be interested in seeing (though that could be shared with us separately in Zulip)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some sort of panic occurred. I'll try to replicate it when I work on this and post it on Zulip. Probably won't be until Friday or Saturday though.

Copy link
Contributor Author

@RossSmyth RossSmyth Mar 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, here's a branch with the panic: https://github.com/RossSmyth/rust/tree/rustfmt_test_panic

It be may as designed? Looking at it again after just running x.py test rustfmt

Ran 589 system tests.
assertion `left == right` failed: 1 system tests failed
  left: 1
 right: 0
Failed to join a test thread: Any { .. }


failures:
    test::system_tests

test result: FAILED. 171 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out; finished in 7.15s

There are two panics that occur.

  1. // Display results.
    println!("Ran {} idempotent tests.", count);
    assert_eq!(fails, 0, "{} idempotent tests failed", fails);

Fails with left == right failed: 1 idempotent tests failed

  1. fn run_test_with<F>(test_setting: &TestSetting, f: F)
    where
    F: FnOnce(),
    F: Send + 'static,
    {
    thread::Builder::new()
    .stack_size(test_setting.stack_size)
    .spawn(f)
    .expect("Failed to create a test thread")
    .join()
    .expect("Failed to join a test thread")
    }

Fails with panicked at src\\tools\\rustfmt\\src\\test\\mod.rs:76:10:\nFailed to join a test thread: Any { .. }

Is it designed to just panic if a test fails? It also dumps a pretty large JSON object out.

}
ast::ExprKind::Path(ref qself, ref path) => {
rewrite_path(context, PathContext::Expr, qself, path, shape)
Expand Down Expand Up @@ -625,7 +625,7 @@ pub(crate) fn rewrite_cond(
shape: Shape,
) -> Option<String> {
match expr.kind {
ast::ExprKind::Match(ref cond, _) => {
ast::ExprKind::Match(ref cond, _, MatchKind::Prefix) => {
// `match `cond` {`
let cond_shape = match context.config.indent_style() {
IndentStyle::Visual => shape.shrink_left(6).and_then(|s| s.sub_width(2))?,
Expand Down
Loading
Loading