Skip to content

Parse guard patterns #133424

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

Merged
merged 8 commits into from
Dec 9, 2024
Merged
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: 8 additions & 3 deletions compiler/rustc_ast/src/ast.rs
Original file line number Diff line number Diff line change
@@ -632,9 +632,11 @@ impl Pat {
| PatKind::Or(s) => s.iter().for_each(|p| p.walk(it)),

// Trivial wrappers over inner patterns.
PatKind::Box(s) | PatKind::Deref(s) | PatKind::Ref(s, _) | PatKind::Paren(s) => {
s.walk(it)
}
PatKind::Box(s)
| PatKind::Deref(s)
| PatKind::Ref(s, _)
| PatKind::Paren(s)
| PatKind::Guard(s, _) => s.walk(it),

// These patterns do not contain subpatterns, skip.
PatKind::Wild
@@ -844,6 +846,9 @@ pub enum PatKind {
// A never pattern `!`.
Never,

/// A guard pattern (e.g., `x if guard(x)`).
Guard(P<Pat>, P<Expr>),

/// Parentheses in patterns used for grouping (i.e., `(PAT)`).
Paren(P<Pat>),

4 changes: 4 additions & 0 deletions compiler/rustc_ast/src/mut_visit.rs
Original file line number Diff line number Diff line change
@@ -1520,6 +1520,10 @@ pub fn walk_pat<T: MutVisitor>(vis: &mut T, pat: &mut P<Pat>) {
visit_opt(e2, |e| vis.visit_expr(e));
vis.visit_span(span);
}
PatKind::Guard(p, e) => {
vis.visit_pat(p);
vis.visit_expr(e);
}
PatKind::Tuple(elems) | PatKind::Slice(elems) | PatKind::Or(elems) => {
visit_thin_vec(elems, |elem| vis.visit_pat(elem))
}
4 changes: 4 additions & 0 deletions compiler/rustc_ast/src/visit.rs
Original file line number Diff line number Diff line change
@@ -679,6 +679,10 @@ pub fn walk_pat<'a, V: Visitor<'a>>(visitor: &mut V, pattern: &'a Pat) -> V::Res
visit_opt!(visitor, visit_expr, lower_bound);
visit_opt!(visitor, visit_expr, upper_bound);
}
PatKind::Guard(subpattern, guard_condition) => {
try_visit!(visitor.visit_pat(subpattern));
try_visit!(visitor.visit_expr(guard_condition));
}
PatKind::Wild | PatKind::Rest | PatKind::Never => {}
PatKind::Err(_guar) => {}
PatKind::Tuple(elems) | PatKind::Slice(elems) | PatKind::Or(elems) => {
2 changes: 2 additions & 0 deletions compiler/rustc_ast_lowering/src/pat.rs
Original file line number Diff line number Diff line change
@@ -114,6 +114,8 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
self.lower_range_end(end, e2.is_some()),
);
}
// FIXME(guard_patterns): lower pattern guards to HIR
PatKind::Guard(inner, _) => pattern = inner,
PatKind::Slice(pats) => break self.lower_pat_slice(pats),
PatKind::Rest => {
// If we reach here the `..` pattern is not semantically allowed.
1 change: 1 addition & 0 deletions compiler/rustc_ast_passes/src/feature_gate.rs
Original file line number Diff line number Diff line change
@@ -551,6 +551,7 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session, features: &Features) {
gate_all!(builtin_syntax, "`builtin #` syntax is unstable");
gate_all!(explicit_tail_calls, "`become` expression is experimental");
gate_all!(generic_const_items, "generic const items are experimental");
gate_all!(guard_patterns, "guard patterns are experimental", "consider using match arm guards");
gate_all!(fn_delegation, "functions delegation is not yet fully implemented");
gate_all!(postfix_match, "postfix match is experimental");
gate_all!(mut_ref, "mutable by-reference bindings are experimental");
8 changes: 8 additions & 0 deletions compiler/rustc_ast_pretty/src/pprust/state.rs
Original file line number Diff line number Diff line change
@@ -1709,6 +1709,14 @@ impl<'a> State<'a> {
self.print_expr(e, FixupContext::default());
}
}
PatKind::Guard(subpat, condition) => {
self.popen();
self.print_pat(subpat);
self.space();
self.word_space("if");
self.print_expr(condition, FixupContext::default());
self.pclose();
}
PatKind::Slice(elts) => {
self.word("[");
self.commasep(Inconsistent, elts, |s, p| s.print_pat(p));
2 changes: 1 addition & 1 deletion compiler/rustc_expand/src/expand.rs
Original file line number Diff line number Diff line change
@@ -990,7 +990,7 @@ pub fn parse_ast_fragment<'a>(
}
}
AstFragmentKind::Ty => AstFragment::Ty(this.parse_ty()?),
AstFragmentKind::Pat => AstFragment::Pat(this.parse_pat_allow_top_alt(
AstFragmentKind::Pat => AstFragment::Pat(this.parse_pat_allow_top_guard(
None,
RecoverComma::No,
RecoverColon::Yes,
2 changes: 2 additions & 0 deletions compiler/rustc_feature/src/unstable.rs
Original file line number Diff line number Diff line change
@@ -504,6 +504,8 @@ declare_features! (
(incomplete, generic_const_items, "1.73.0", Some(113521)),
/// Allows registering static items globally, possibly across crates, to iterate over at runtime.
(unstable, global_registration, "1.80.0", Some(125119)),
/// Allows using guards in patterns.
(incomplete, guard_patterns, "CURRENT_RUSTC_VERSION", Some(129967)),
/// Allows using `..=X` as a patterns in slices.
(unstable, half_open_range_patterns_in_slices, "1.66.0", Some(67264)),
/// Allows `if let` guard in match arms.
2 changes: 1 addition & 1 deletion compiler/rustc_lint/src/unused.rs
Original file line number Diff line number Diff line change
@@ -1235,7 +1235,7 @@ impl EarlyLintPass for UnusedParens {
self.check_unused_parens_pat(cx, &f.pat, false, false, keep_space);
},
// Avoid linting on `i @ (p0 | .. | pn)` and `box (p0 | .. | pn)`, #64106.
Ident(.., Some(p)) | Box(p) | Deref(p) => self.check_unused_parens_pat(cx, p, true, false, keep_space),
Ident(.., Some(p)) | Box(p) | Deref(p) | Guard(p, _) => self.check_unused_parens_pat(cx, p, true, false, keep_space),
// Avoid linting on `&(mut x)` as `&mut x` has a different meaning, #55342.
// Also avoid linting on `& mut? (p0 | .. | pn)`, #64106.
Ref(p, m) => self.check_unused_parens_pat(cx, p, true, *m == Mutability::Not, keep_space),
58 changes: 26 additions & 32 deletions compiler/rustc_parse/src/parser/expr.rs
Original file line number Diff line number Diff line change
@@ -2630,7 +2630,7 @@ impl<'a> Parser<'a> {
};
self.bump(); // Eat `let` token
let lo = self.prev_token.span;
let pat = self.parse_pat_allow_top_alt(
let pat = self.parse_pat_no_top_guard(
None,
RecoverComma::Yes,
RecoverColon::Yes,
@@ -2776,7 +2776,7 @@ impl<'a> Parser<'a> {
};
// Try to parse the pattern `for ($PAT) in $EXPR`.
let pat = match (
self.parse_pat_allow_top_alt(
self.parse_pat_allow_top_guard(
None,
RecoverComma::Yes,
RecoverColon::Yes,
@@ -3239,7 +3239,7 @@ impl<'a> Parser<'a> {
// then we should recover.
let mut snapshot = this.create_snapshot_for_diagnostic();
let pattern_follows = snapshot
.parse_pat_allow_top_alt(
.parse_pat_no_top_guard(
None,
RecoverComma::Yes,
RecoverColon::Yes,
@@ -3313,43 +3313,37 @@ impl<'a> Parser<'a> {

fn parse_match_arm_pat_and_guard(&mut self) -> PResult<'a, (P<Pat>, Option<P<Expr>>)> {
if self.token == token::OpenDelim(Delimiter::Parenthesis) {
// Detect and recover from `($pat if $cond) => $arm`.
let left = self.token.span;
match self.parse_pat_allow_top_alt(
let pat = self.parse_pat_no_top_guard(
None,
RecoverComma::Yes,
RecoverColon::Yes,
CommaRecoveryMode::EitherTupleOrPipe,
) {
Ok(pat) => Ok((pat, self.parse_match_arm_guard()?)),
Err(err)
if let prev_sp = self.prev_token.span
&& let true = self.eat_keyword(kw::If) =>
{
// We know for certain we've found `($pat if` so far.
let mut cond = match self.parse_match_guard_condition() {
Ok(cond) => cond,
Err(cond_err) => {
cond_err.cancel();
return Err(err);
}
};
err.cancel();
CondChecker::new(self).visit_expr(&mut cond);
self.eat_to_tokens(&[&token::CloseDelim(Delimiter::Parenthesis)]);
self.expect(&token::CloseDelim(Delimiter::Parenthesis))?;
let right = self.prev_token.span;
self.dcx().emit_err(errors::ParenthesesInMatchPat {
span: vec![left, right],
sugg: errors::ParenthesesInMatchPatSugg { left, right },
});
Ok((self.mk_pat(left.to(prev_sp), ast::PatKind::Wild), Some(cond)))
}
Err(err) => Err(err),
)?;
if let ast::PatKind::Paren(subpat) = &pat.kind
&& let ast::PatKind::Guard(..) = &subpat.kind
{
// Detect and recover from `($pat if $cond) => $arm`.
// FIXME(guard_patterns): convert this to a normal guard instead
let span = pat.span;
let ast::PatKind::Paren(subpat) = pat.into_inner().kind else { unreachable!() };
let ast::PatKind::Guard(_, mut cond) = subpat.into_inner().kind else {
unreachable!()
};
self.psess.gated_spans.ungate_last(sym::guard_patterns, cond.span);
CondChecker::new(self).visit_expr(&mut cond);
let right = self.prev_token.span;
self.dcx().emit_err(errors::ParenthesesInMatchPat {
span: vec![left, right],
sugg: errors::ParenthesesInMatchPatSugg { left, right },
});
Ok((self.mk_pat(span, ast::PatKind::Wild), Some(cond)))
} else {
Ok((pat, self.parse_match_arm_guard()?))
}
} else {
// Regular parser flow:
let pat = self.parse_pat_allow_top_alt(
let pat = self.parse_pat_no_top_guard(
None,
RecoverComma::Yes,
RecoverColon::Yes,
2 changes: 1 addition & 1 deletion compiler/rustc_parse/src/parser/nonterminal.rs
Original file line number Diff line number Diff line change
@@ -174,7 +174,7 @@ impl<'a> Parser<'a> {
NonterminalKind::Pat(pat_kind) => {
NtPat(self.collect_tokens_no_attrs(|this| match pat_kind {
PatParam { .. } => this.parse_pat_no_top_alt(None, None),
PatWithOr => this.parse_pat_allow_top_alt(
PatWithOr => this.parse_pat_no_top_guard(
None,
RecoverComma::No,
RecoverColon::No,
54 changes: 40 additions & 14 deletions compiler/rustc_parse/src/parser/pat.rs
Original file line number Diff line number Diff line change
@@ -97,9 +97,34 @@ pub enum PatternLocation {
impl<'a> Parser<'a> {
/// Parses a pattern.
///
/// Corresponds to `pat<no_top_alt>` in RFC 2535 and does not admit or-patterns
/// at the top level. Used when parsing the parameters of lambda expressions,
/// functions, function pointers, and `pat` macro fragments.
/// Corresponds to `Pattern` in RFC 3637 and admits guard patterns at the top level.
/// Used when parsing patterns in all cases where neither `PatternNoTopGuard` nor
/// `PatternNoTopAlt` (see below) are used.
pub fn parse_pat_allow_top_guard(
&mut self,
expected: Option<Expected>,
rc: RecoverComma,
ra: RecoverColon,
rt: CommaRecoveryMode,
) -> PResult<'a, P<Pat>> {
let pat = self.parse_pat_no_top_guard(expected, rc, ra, rt)?;

if self.eat_keyword(kw::If) {
let cond = self.parse_expr()?;
// Feature-gate guard patterns
self.psess.gated_spans.gate(sym::guard_patterns, cond.span);
let span = pat.span.to(cond.span);
Ok(self.mk_pat(span, PatKind::Guard(pat, cond)))
} else {
Ok(pat)
}
}

/// Parses a pattern.
///
/// Corresponds to `PatternNoTopAlt` in RFC 3637 and does not admit or-patterns
/// or guard patterns at the top level. Used when parsing the parameters of lambda
/// expressions, functions, function pointers, and `pat_param` macro fragments.
pub fn parse_pat_no_top_alt(
&mut self,
expected: Option<Expected>,
@@ -110,25 +135,26 @@ impl<'a> Parser<'a> {

/// Parses a pattern.
///
/// Corresponds to `top_pat` in RFC 2535 and allows or-pattern at the top level.
/// Used for parsing patterns in all cases when `pat<no_top_alt>` is not used.
/// Corresponds to `PatternNoTopGuard` in RFC 3637 and allows or-patterns, but not
/// guard patterns, at the top level. Used for parsing patterns in `pat` fragments (until
/// the next edition) and `let`, `if let`, and `while let` expressions.
///
/// Note that after the FCP in <https://github.com/rust-lang/rust/issues/81415>,
/// a leading vert is allowed in nested or-patterns, too. This allows us to
/// simplify the grammar somewhat.
pub fn parse_pat_allow_top_alt(
pub fn parse_pat_no_top_guard(
&mut self,
expected: Option<Expected>,
rc: RecoverComma,
ra: RecoverColon,
rt: CommaRecoveryMode,
) -> PResult<'a, P<Pat>> {
self.parse_pat_allow_top_alt_inner(expected, rc, ra, rt, None).map(|(pat, _)| pat)
self.parse_pat_no_top_guard_inner(expected, rc, ra, rt, None).map(|(pat, _)| pat)
}

/// Returns the pattern and a bool indicating whether we recovered from a trailing vert (true =
/// recovered).
fn parse_pat_allow_top_alt_inner(
fn parse_pat_no_top_guard_inner(
&mut self,
expected: Option<Expected>,
rc: RecoverComma,
@@ -229,7 +255,7 @@ impl<'a> Parser<'a> {
// We use `parse_pat_allow_top_alt` regardless of whether we actually want top-level
// or-patterns so that we can detect when a user tries to use it. This allows us to print a
// better error message.
let (pat, trailing_vert) = self.parse_pat_allow_top_alt_inner(
let (pat, trailing_vert) = self.parse_pat_no_top_guard_inner(
expected,
rc,
RecoverColon::No,
@@ -696,7 +722,7 @@ impl<'a> Parser<'a> {
} else if self.check(&token::OpenDelim(Delimiter::Bracket)) {
// Parse `[pat, pat,...]` as a slice pattern.
let (pats, _) = self.parse_delim_comma_seq(Delimiter::Bracket, |p| {
p.parse_pat_allow_top_alt(
p.parse_pat_allow_top_guard(
None,
RecoverComma::No,
RecoverColon::No,
@@ -944,7 +970,7 @@ impl<'a> Parser<'a> {
let open_paren = self.token.span;

let (fields, trailing_comma) = self.parse_paren_comma_seq(|p| {
p.parse_pat_allow_top_alt(
p.parse_pat_allow_top_guard(
None,
RecoverComma::No,
RecoverColon::No,
@@ -1359,7 +1385,7 @@ impl<'a> Parser<'a> {
path: Path,
) -> PResult<'a, PatKind> {
let (fields, _) = self.parse_paren_comma_seq(|p| {
p.parse_pat_allow_top_alt(
p.parse_pat_allow_top_guard(
None,
RecoverComma::No,
RecoverColon::No,
@@ -1394,7 +1420,7 @@ impl<'a> Parser<'a> {
self.parse_builtin(|self_, _lo, ident| {
Ok(match ident.name {
// builtin#deref(PAT)
sym::deref => Some(ast::PatKind::Deref(self_.parse_pat_allow_top_alt(
sym::deref => Some(ast::PatKind::Deref(self_.parse_pat_allow_top_guard(
None,
RecoverComma::Yes,
RecoverColon::Yes,
@@ -1669,7 +1695,7 @@ impl<'a> Parser<'a> {
// Parsing a pattern of the form `fieldname: pat`.
let fieldname = self.parse_field_name()?;
self.bump();
let pat = self.parse_pat_allow_top_alt(
let pat = self.parse_pat_allow_top_guard(
None,
RecoverComma::No,
RecoverColon::No,
2 changes: 1 addition & 1 deletion compiler/rustc_parse/src/parser/path.rs
Original file line number Diff line number Diff line change
@@ -469,7 +469,7 @@ impl<'a> Parser<'a> {
PathStyle::Pat
if let Ok(_) = self
.parse_paren_comma_seq(|p| {
p.parse_pat_allow_top_alt(
p.parse_pat_allow_top_guard(
None,
RecoverComma::No,
RecoverColon::No,
1 change: 1 addition & 0 deletions compiler/rustc_passes/src/input_stats.rs
Original file line number Diff line number Diff line change
@@ -556,6 +556,7 @@ impl<'v> ast_visit::Visitor<'v> for StatCollector<'v> {
Slice,
Rest,
Never,
Guard,
Paren,
MacCall,
Err
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
@@ -985,6 +985,7 @@ symbols! {
global_registration,
globs,
gt,
guard_patterns,
half_open_range_patterns,
half_open_range_patterns_in_slices,
hash,
2 changes: 1 addition & 1 deletion src/tools/clippy/clippy_lints/src/unnested_or_patterns.rs
Original file line number Diff line number Diff line change
@@ -234,7 +234,7 @@ fn transform_with_focus_on_idx(alternatives: &mut ThinVec<P<Pat>>, focus_idx: us
// In the case of only two patterns, replacement adds net characters.
| Ref(_, Mutability::Not)
// Dealt with elsewhere.
| Or(_) | Paren(_) | Deref(_) => false,
| Or(_) | Paren(_) | Deref(_) | Guard(..) => false,
// Transform `box x | ... | box y` into `box (x | y)`.
//
// The cases below until `Slice(...)` deal with *singleton* products.
6 changes: 4 additions & 2 deletions src/tools/rustfmt/src/patterns.rs
Original file line number Diff line number Diff line change
@@ -48,7 +48,8 @@ fn is_short_pattern_inner(pat: &ast::Pat) -> bool {
| ast::PatKind::MacCall(..)
| ast::PatKind::Slice(..)
| ast::PatKind::Path(..)
| ast::PatKind::Range(..) => false,
| ast::PatKind::Range(..)
| ast::PatKind::Guard(..) => false,
ast::PatKind::Tuple(ref subpats) => subpats.len() <= 1,
ast::PatKind::TupleStruct(_, ref path, ref subpats) => {
path.segments.len() <= 1 && subpats.len() <= 1
@@ -338,8 +339,9 @@ impl Rewrite for Pat {
.max_width_error(shape.width, self.span)?,
)
.map(|inner_pat| format!("({})", inner_pat)),
PatKind::Err(_) => Err(RewriteError::Unknown),
PatKind::Guard(..) => Ok(context.snippet(self.span).to_string()),
PatKind::Deref(_) => Err(RewriteError::Unknown),
PatKind::Err(_) => Err(RewriteError::Unknown),
}
}
}
Loading