Skip to content

Commit 2b399b5

Browse files
committed
Auto merge of rust-lang#118527 - Nadrieril:never_patterns_parse, r=compiler-errors
never_patterns: Parse match arms with no body Never patterns are meant to signal unreachable cases, and thus don't take bodies: ```rust let ptr: *const Option<!> = ...; match *ptr { None => { foo(); } Some(!), } ``` This PR makes rustc accept the above, and enforces that an arm has a body xor is a never pattern. This affects parsing of match arms even with the feature off, so this is delicate. (Plus this is my first non-trivial change to the parser). ~~The last commit is optional; it introduces a bit of churn to allow the new suggestions to be machine-applicable. There may be a better solution? I'm not sure.~~ EDIT: I removed that commit r? `@compiler-errors`
2 parents ae612be + 431cc4a commit 2b399b5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+772
-236
lines changed

compiler/rustc_ast/src/ast.rs

+20-2
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,24 @@ impl Pat {
658658
pub fn is_rest(&self) -> bool {
659659
matches!(self.kind, PatKind::Rest)
660660
}
661+
662+
/// Whether this could be a never pattern, taking into account that a macro invocation can
663+
/// return a never pattern. Used to inform errors during parsing.
664+
pub fn could_be_never_pattern(&self) -> bool {
665+
let mut could_be_never_pattern = false;
666+
self.walk(&mut |pat| match &pat.kind {
667+
PatKind::Never | PatKind::MacCall(_) => {
668+
could_be_never_pattern = true;
669+
false
670+
}
671+
PatKind::Or(s) => {
672+
could_be_never_pattern = s.iter().all(|p| p.could_be_never_pattern());
673+
false
674+
}
675+
_ => true,
676+
});
677+
could_be_never_pattern
678+
}
661679
}
662680

663681
/// A single field in a struct pattern.
@@ -1080,8 +1098,8 @@ pub struct Arm {
10801098
pub pat: P<Pat>,
10811099
/// Match arm guard, e.g. `n > 10` in `match foo { n if n > 10 => {}, _ => {} }`
10821100
pub guard: Option<P<Expr>>,
1083-
/// Match arm body.
1084-
pub body: P<Expr>,
1101+
/// Match arm body. Omitted if the pattern is a never pattern.
1102+
pub body: Option<P<Expr>>,
10851103
pub span: Span,
10861104
pub id: NodeId,
10871105
pub is_placeholder: bool,

compiler/rustc_ast/src/mut_visit.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,7 @@ pub fn noop_flat_map_arm<T: MutVisitor>(mut arm: Arm, vis: &mut T) -> SmallVec<[
453453
vis.visit_id(id);
454454
vis.visit_pat(pat);
455455
visit_opt(guard, |guard| vis.visit_expr(guard));
456-
vis.visit_expr(body);
456+
visit_opt(body, |body| vis.visit_expr(body));
457457
vis.visit_span(span);
458458
smallvec![arm]
459459
}

compiler/rustc_ast/src/visit.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -951,7 +951,7 @@ pub fn walk_param<'a, V: Visitor<'a>>(visitor: &mut V, param: &'a Param) {
951951
pub fn walk_arm<'a, V: Visitor<'a>>(visitor: &mut V, arm: &'a Arm) {
952952
visitor.visit_pat(&arm.pat);
953953
walk_list!(visitor, visit_expr, &arm.guard);
954-
visitor.visit_expr(&arm.body);
954+
walk_list!(visitor, visit_expr, &arm.body);
955955
walk_list!(visitor, visit_attribute, &arm.attrs);
956956
}
957957

compiler/rustc_ast_lowering/messages.ftl

+13
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ ast_lowering_invalid_register =
9191
ast_lowering_invalid_register_class =
9292
invalid register class `{$reg_class}`: {$error}
9393
94+
ast_lowering_match_arm_with_no_body =
95+
`match` arm with no body
96+
.suggestion = add a body after the pattern
97+
9498
ast_lowering_misplaced_assoc_ty_binding =
9599
associated type bounds are only allowed in where clauses and function signatures, not in {$position}
96100
@@ -104,6 +108,15 @@ ast_lowering_misplaced_impl_trait =
104108
ast_lowering_misplaced_relax_trait_bound =
105109
`?Trait` bounds are only permitted at the point where a type parameter is declared
106110
111+
ast_lowering_never_pattern_with_body =
112+
a never pattern is always unreachable
113+
.label = this will never be executed
114+
.suggestion = remove this expression
115+
116+
ast_lowering_never_pattern_with_guard =
117+
a guard on a never pattern will never be run
118+
.suggestion = remove this guard
119+
107120
ast_lowering_not_supported_for_lifetime_binder_async_closure =
108121
`for<...>` binders on `async` closures are not currently supported
109122

compiler/rustc_ast_lowering/src/errors.rs

+26
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,32 @@ pub struct NotSupportedForLifetimeBinderAsyncClosure {
340340
pub span: Span,
341341
}
342342

343+
#[derive(Diagnostic)]
344+
#[diag(ast_lowering_match_arm_with_no_body)]
345+
pub struct MatchArmWithNoBody {
346+
#[primary_span]
347+
pub span: Span,
348+
#[suggestion(code = " => todo!(),", applicability = "has-placeholders")]
349+
pub suggestion: Span,
350+
}
351+
352+
#[derive(Diagnostic)]
353+
#[diag(ast_lowering_never_pattern_with_body)]
354+
pub struct NeverPatternWithBody {
355+
#[primary_span]
356+
#[label]
357+
#[suggestion(code = "", applicability = "maybe-incorrect")]
358+
pub span: Span,
359+
}
360+
361+
#[derive(Diagnostic)]
362+
#[diag(ast_lowering_never_pattern_with_guard)]
363+
pub struct NeverPatternWithGuard {
364+
#[primary_span]
365+
#[suggestion(code = "", applicability = "maybe-incorrect")]
366+
pub span: Span,
367+
}
368+
343369
#[derive(Diagnostic, Clone, Copy)]
344370
#[diag(ast_lowering_arbitrary_expression_in_pattern)]
345371
pub struct ArbitraryExpressionInPattern {

compiler/rustc_ast_lowering/src/expr.rs

+40-10
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use super::errors::{
22
AsyncCoroutinesNotSupported, AsyncNonMoveClosureNotSupported, AwaitOnlyInAsyncFnAndBlocks,
33
BaseExpressionDoubleDot, ClosureCannotBeStatic, CoroutineTooManyParameters,
4-
FunctionalRecordUpdateDestructuringAssignment, InclusiveRangeWithNoEnd,
5-
NotSupportedForLifetimeBinderAsyncClosure, UnderscoreExprLhsAssign,
4+
FunctionalRecordUpdateDestructuringAssignment, InclusiveRangeWithNoEnd, MatchArmWithNoBody,
5+
NeverPatternWithBody, NeverPatternWithGuard, NotSupportedForLifetimeBinderAsyncClosure,
6+
UnderscoreExprLhsAssign,
67
};
78
use super::ResolverAstLoweringExt;
89
use super::{ImplTraitContext, LoweringContext, ParamMode, ParenthesizedGenericArgs};
@@ -549,7 +550,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
549550

550551
fn lower_arm(&mut self, arm: &Arm) -> hir::Arm<'hir> {
551552
let pat = self.lower_pat(&arm.pat);
552-
let guard = arm.guard.as_ref().map(|cond| {
553+
let mut guard = arm.guard.as_ref().map(|cond| {
553554
if let ExprKind::Let(pat, scrutinee, span, is_recovered) = &cond.kind {
554555
hir::Guard::IfLet(self.arena.alloc(hir::Let {
555556
hir_id: self.next_id(),
@@ -564,14 +565,43 @@ impl<'hir> LoweringContext<'_, 'hir> {
564565
}
565566
});
566567
let hir_id = self.next_id();
568+
let span = self.lower_span(arm.span);
567569
self.lower_attrs(hir_id, &arm.attrs);
568-
hir::Arm {
569-
hir_id,
570-
pat,
571-
guard,
572-
body: self.lower_expr(&arm.body),
573-
span: self.lower_span(arm.span),
574-
}
570+
let is_never_pattern = pat.is_never_pattern();
571+
let body = if let Some(body) = &arm.body
572+
&& !is_never_pattern
573+
{
574+
self.lower_expr(body)
575+
} else {
576+
// Either `body.is_none()` or `is_never_pattern` here.
577+
if !is_never_pattern {
578+
let suggestion = span.shrink_to_hi();
579+
self.tcx.sess.emit_err(MatchArmWithNoBody { span, suggestion });
580+
} else if let Some(body) = &arm.body {
581+
self.tcx.sess.emit_err(NeverPatternWithBody { span: body.span });
582+
guard = None;
583+
} else if let Some(g) = &arm.guard {
584+
self.tcx.sess.emit_err(NeverPatternWithGuard { span: g.span });
585+
guard = None;
586+
}
587+
588+
// We add a fake `loop {}` arm body so that it typecks to `!`.
589+
// FIXME(never_patterns): Desugar into a call to `unreachable_unchecked`.
590+
let block = self.arena.alloc(hir::Block {
591+
stmts: &[],
592+
expr: None,
593+
hir_id: self.next_id(),
594+
rules: hir::BlockCheckMode::DefaultBlock,
595+
span,
596+
targeted_by_break: false,
597+
});
598+
self.arena.alloc(hir::Expr {
599+
hir_id: self.next_id(),
600+
kind: hir::ExprKind::Loop(block, None, hir::LoopSource::Loop, span),
601+
span,
602+
})
603+
};
604+
hir::Arm { hir_id, pat, guard, body, span }
575605
}
576606

577607
/// Lower an `async` construct to a coroutine that implements `Future`.

compiler/rustc_ast_pretty/src/pprust/state/expr.rs

+21-16
Original file line numberDiff line numberDiff line change
@@ -631,28 +631,33 @@ impl<'a> State<'a> {
631631
self.print_expr(e);
632632
self.space();
633633
}
634-
self.word_space("=>");
635634

636-
match &arm.body.kind {
637-
ast::ExprKind::Block(blk, opt_label) => {
638-
if let Some(label) = opt_label {
639-
self.print_ident(label.ident);
640-
self.word_space(":");
641-
}
635+
if let Some(body) = &arm.body {
636+
self.word_space("=>");
637+
638+
match &body.kind {
639+
ast::ExprKind::Block(blk, opt_label) => {
640+
if let Some(label) = opt_label {
641+
self.print_ident(label.ident);
642+
self.word_space(":");
643+
}
642644

643-
// The block will close the pattern's ibox.
644-
self.print_block_unclosed_indent(blk);
645+
// The block will close the pattern's ibox.
646+
self.print_block_unclosed_indent(blk);
645647

646-
// If it is a user-provided unsafe block, print a comma after it.
647-
if let BlockCheckMode::Unsafe(ast::UserProvided) = blk.rules {
648+
// If it is a user-provided unsafe block, print a comma after it.
649+
if let BlockCheckMode::Unsafe(ast::UserProvided) = blk.rules {
650+
self.word(",");
651+
}
652+
}
653+
_ => {
654+
self.end(); // Close the ibox for the pattern.
655+
self.print_expr(body);
648656
self.word(",");
649657
}
650658
}
651-
_ => {
652-
self.end(); // Close the ibox for the pattern.
653-
self.print_expr(&arm.body);
654-
self.word(",");
655-
}
659+
} else {
660+
self.word(",");
656661
}
657662
self.end(); // Close enclosing cbox.
658663
}

compiler/rustc_builtin_macros/src/deriving/cmp/partial_ord.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ fn cs_partial_cmp(
136136
&& let Some(last) = arms.last_mut()
137137
&& let PatKind::Wild = last.pat.kind
138138
{
139-
last.body = expr2;
139+
last.body = Some(expr2);
140140
expr1
141141
} else {
142142
let eq_arm = cx.arm(

compiler/rustc_expand/messages.ftl

+2
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ expand_macro_const_stability =
7171
.label = invalid const stability attribute
7272
.label2 = const stability attribute affects this macro
7373
74+
expand_macro_expands_to_match_arm = macros cannot expand to match arms
75+
7476
expand_malformed_feature_attribute =
7577
malformed `feature` attribute input
7678
.expected = expected just one word

compiler/rustc_expand/src/build.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,7 @@ impl<'a> ExtCtxt<'a> {
505505
attrs: AttrVec::new(),
506506
pat,
507507
guard: None,
508-
body: expr,
508+
body: Some(expr),
509509
span,
510510
id: ast::DUMMY_NODE_ID,
511511
is_placeholder: false,

compiler/rustc_expand/src/errors.rs

+2
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,8 @@ pub(crate) struct IncompleteParse<'a> {
304304
pub label_span: Span,
305305
pub macro_path: &'a ast::Path,
306306
pub kind_name: &'a str,
307+
#[note(expand_macro_expands_to_match_arm)]
308+
pub expands_to_match_arm: Option<()>,
307309

308310
#[suggestion(
309311
expand_suggestion_add_semi,

compiler/rustc_expand/src/expand.rs

+3
Original file line numberDiff line numberDiff line change
@@ -955,12 +955,15 @@ pub fn ensure_complete_parse<'a>(
955955
_ => None,
956956
};
957957

958+
let expands_to_match_arm = kind_name == "pattern" && parser.token == token::FatArrow;
959+
958960
parser.sess.emit_err(IncompleteParse {
959961
span: def_site_span,
960962
token,
961963
label_span: span,
962964
macro_path,
963965
kind_name,
966+
expands_to_match_arm: expands_to_match_arm.then_some(()),
964967
add_semicolon,
965968
});
966969
}

compiler/rustc_expand/src/placeholders.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ pub fn placeholder(
119119
}]),
120120
AstFragmentKind::Arms => AstFragment::Arms(smallvec![ast::Arm {
121121
attrs: Default::default(),
122-
body: expr_placeholder(),
122+
body: Some(expr_placeholder()),
123123
guard: None,
124124
id,
125125
pat: pat(),

compiler/rustc_hir/src/hir.rs

+17
Original file line numberDiff line numberDiff line change
@@ -1056,6 +1056,23 @@ impl<'hir> Pat<'hir> {
10561056
true
10571057
})
10581058
}
1059+
1060+
/// Whether this a never pattern.
1061+
pub fn is_never_pattern(&self) -> bool {
1062+
let mut is_never_pattern = false;
1063+
self.walk(|pat| match &pat.kind {
1064+
PatKind::Never => {
1065+
is_never_pattern = true;
1066+
false
1067+
}
1068+
PatKind::Or(s) => {
1069+
is_never_pattern = s.iter().all(|p| p.is_never_pattern());
1070+
false
1071+
}
1072+
_ => true,
1073+
});
1074+
is_never_pattern
1075+
}
10591076
}
10601077

10611078
/// A single field in a struct pattern.

compiler/rustc_lint/src/builtin.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -1000,8 +1000,10 @@ impl EarlyLintPass for UnusedDocComment {
10001000
}
10011001

10021002
fn check_arm(&mut self, cx: &EarlyContext<'_>, arm: &ast::Arm) {
1003-
let arm_span = arm.pat.span.with_hi(arm.body.span.hi());
1004-
warn_if_doc(cx, arm_span, "match arms", &arm.attrs);
1003+
if let Some(body) = &arm.body {
1004+
let arm_span = arm.pat.span.with_hi(body.span.hi());
1005+
warn_if_doc(cx, arm_span, "match arms", &arm.attrs);
1006+
}
10051007
}
10061008

10071009
fn check_pat(&mut self, cx: &EarlyContext<'_>, pat: &ast::Pat) {

compiler/rustc_lint/src/unused.rs

+11-9
Original file line numberDiff line numberDiff line change
@@ -1113,15 +1113,17 @@ impl EarlyLintPass for UnusedParens {
11131113
}
11141114
ExprKind::Match(ref _expr, ref arm) => {
11151115
for a in arm {
1116-
self.check_unused_delims_expr(
1117-
cx,
1118-
&a.body,
1119-
UnusedDelimsCtx::MatchArmExpr,
1120-
false,
1121-
None,
1122-
None,
1123-
true,
1124-
);
1116+
if let Some(body) = &a.body {
1117+
self.check_unused_delims_expr(
1118+
cx,
1119+
body,
1120+
UnusedDelimsCtx::MatchArmExpr,
1121+
false,
1122+
None,
1123+
None,
1124+
true,
1125+
);
1126+
}
11251127
}
11261128
}
11271129
_ => {}

compiler/rustc_parse/messages.ftl

-2
Original file line numberDiff line numberDiff line change
@@ -458,8 +458,6 @@ parse_macro_expands_to_adt_field = macros cannot expand to {$adt_ty} fields
458458
459459
parse_macro_expands_to_enum_variant = macros cannot expand to enum variants
460460
461-
parse_macro_expands_to_match_arm = macros cannot expand to match arms
462-
463461
parse_macro_invocation_visibility = can't qualify macro invocation with `pub`
464462
.suggestion = remove the visibility
465463
.help = try adjusting the macro to put `{$vis}` inside the invocation

0 commit comments

Comments
 (0)