diff --git a/compiler/rustc_ast/src/token.rs b/compiler/rustc_ast/src/token.rs index efa2a008e65ae..0144aabb24413 100644 --- a/compiler/rustc_ast/src/token.rs +++ b/compiler/rustc_ast/src/token.rs @@ -308,8 +308,13 @@ impl LitKind { } pub fn ident_can_begin_expr(name: Symbol, span: Span, is_raw: IdentIsRaw) -> bool { + // WARNING: Take care when modifying this function! It will change the stable(!) set of + // tokens that are allowed to match an `expr` nonterminal which is user observable. + let ident_token = Token::new(Ident(name, is_raw), span); + // FIXME: Remove `box` from this list given we officially no longer support box expressions + // (#108471) (needs lang FCP as it affects stable macro matching behavior). !ident_token.is_reserved_ident() || ident_token.is_path_segment_keyword() || [ @@ -340,6 +345,9 @@ pub fn ident_can_begin_expr(name: Symbol, span: Span, is_raw: IdentIsRaw) -> boo } fn ident_can_begin_type(name: Symbol, span: Span, is_raw: IdentIsRaw) -> bool { + // WARNING: Take care when modifying this function! It will change the stable(!) set of + // tokens that are allowed to match an `ty` nonterminal which is user observable. + let ident_token = Token::new(Ident(name, is_raw), span); !ident_token.is_reserved_ident() @@ -661,10 +669,10 @@ impl Token { } /// Returns `true` if the token can appear at the start of an expression. - /// - /// **NB**: Take care when modifying this function, since it will change - /// the stable set of tokens that are allowed to match an expr nonterminal. pub fn can_begin_expr(&self) -> bool { + // WARNING: Take care when modifying this function! It will change the stable(!) set of + // tokens that are allowed to match an `expr` nonterminal which is user observable. + match self.uninterpolate().kind { Ident(name, is_raw) => ident_can_begin_expr(name, self.span, is_raw), // value name or keyword @@ -695,9 +703,10 @@ impl Token { } /// Returns `true` if the token can appear at the start of a pattern. - /// - /// Shamelessly borrowed from `can_begin_expr`. pub fn can_begin_pattern(&self, pat_kind: NtPatKind) -> bool { + // WARNING: Take care when modifying this function! It will change the stable(!) set of + // tokens that are allowed to match an `pat` nonterminal which is user observable. + match &self.uninterpolate().kind { // box, ref, mut, and other identifiers (can stricten) Ident(..) | NtIdent(..) | @@ -727,6 +736,12 @@ impl Token { /// Returns `true` if the token can appear at the start of a type. pub fn can_begin_type(&self) -> bool { + // WARNING: Take care when modifying this function! It will change the stable(!) set of + // tokens that are allowed to match an `ty` nonterminal which is user observable. + + // FIXME: Arguably, `use` should be included in this list since it can begin bare trait + // object types (consider `use<>+` and `use + Trait` for example). + match self.uninterpolate().kind { Ident(name, is_raw) => ident_can_begin_type(name, self.span, is_raw), // type name or keyword diff --git a/compiler/rustc_parse/src/diagnostics.rs b/compiler/rustc_parse/src/diagnostics.rs index 84786cdbf5c1e..a09b2a38593ed 100644 --- a/compiler/rustc_parse/src/diagnostics.rs +++ b/compiler/rustc_parse/src/diagnostics.rs @@ -3728,32 +3728,44 @@ impl HelpUseLatestEdition { } #[derive(Diagnostic)] -#[diag("`box_syntax` has been removed")] -pub(crate) struct BoxSyntaxRemoved { +#[diag("`box` patterns have been removed (feature `box_patterns`)")] +#[help("enable feature `deref_patterns` instead and...")] +pub(crate) struct BoxPatsRemoved { #[primary_span] pub span: Span, + #[suggestion( + "...if possible just remove keyword `box`...", + code = "", + applicability = "maybe-incorrect", + style = "verbose" + )] + pub sugg_removal: Span, #[subdiagnostic] - pub sugg: AddBoxNew, + pub sugg_deref_macro_call: UseDerefMacro, } -#[derive(Subdiagnostic)] -#[multipart_suggestion( - "use `Box::new()` instead", - applicability = "machine-applicable", - style = "verbose" -)] -pub(crate) struct AddBoxNew { - #[suggestion_part(code = "Box::new(")] - pub box_kw_and_lo: Span, - #[suggestion_part(code = ")")] - pub hi: Span, +pub(crate) struct UseDerefMacro { + pub field: Option<(Span, Ident)>, + pub before: Span, + pub after: Span, } -#[derive(Diagnostic)] -#[diag("`box_patterns` has been removed")] -pub(crate) struct BoxPatternsRemoved { - #[primary_span] - pub span: Span, +impl Subdiagnostic for UseDerefMacro { + fn add_to_diag(self, diag: &mut Diag<'_, G>) { + let Self { field, before, after } = self; + + let mut parts = Vec::new(); + if let Some((span, field)) = field { + parts.push((span, format!("{field}: "))); + } + parts.push((before, "deref!(".into())); + parts.push((after, ")".into())); + diag.multipart_suggestion( + "...otherwise replace it with an invocation of macro `deref`", + parts, + Applicability::MaybeIncorrect, + ); + } } #[derive(Diagnostic)] diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 7227c814ce9d6..12de4957e99c2 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -542,9 +542,6 @@ impl<'a> Parser<'a> { let operand_expr = this.parse_expr_dot_or_call(attrs)?; this.recover_from_prefix_increment(operand_expr, pre_span, starts_stmt) } - token::Ident(..) if this.token.is_keyword(kw::Box) => { - make_it!(this, attrs, |this, _| this.parse_expr_box(lo)) - } token::Ident(..) if this.token.is_keyword(kw::Move) && this.look_ahead(1, |t| *t == token::OpenParen) => @@ -582,19 +579,6 @@ impl<'a> Parser<'a> { self.parse_expr_unary(lo, UnOp::Not) } - /// Parse `box expr` - this syntax has been removed, but we still parse this - /// for now to provide a more useful error - fn parse_expr_box(&mut self, box_kw: Span) -> PResult<'a, (Span, ExprKind)> { - self.bump(); // `box` - let (span, expr) = self.parse_expr_prefix_common(box_kw)?; - // Make a multipart suggestion instead of `span_to_snippet` in case source isn't available - let box_kw_and_lo = box_kw.until(self.interpolated_or_expr_span(&expr)); - let hi = span.shrink_to_hi(); - let sugg = diagnostics::AddBoxNew { box_kw_and_lo, hi }; - let guar = self.dcx().emit_err(diagnostics::BoxSyntaxRemoved { span, sugg }); - Ok((span, ExprKind::Err(guar))) - } - fn parse_expr_move(&mut self, move_kw: Span) -> PResult<'a, (Span, ExprKind)> { self.bump(); self.psess.gated_spans.gate(sym::move_expr, move_kw); diff --git a/compiler/rustc_parse/src/parser/pat.rs b/compiler/rustc_parse/src/parser/pat.rs index 65d3de93f7199..64cc8ad9812f2 100644 --- a/compiler/rustc_parse/src/parser/pat.rs +++ b/compiler/rustc_parse/src/parser/pat.rs @@ -1650,8 +1650,15 @@ impl<'a> Parser<'a> { Ok(PatKind::Ident(BindingMode::NONE, Ident::new(kw::Box, box_span), sub)) } else { let pat = Box::new(self.parse_pat_with_range_pat(false, None, None)?); - self.dcx().emit_err(diagnostics::BoxPatternsRemoved { - span: box_span.to(self.prev_token.span), + let before_span = box_span.until(pat.span); + self.dcx().emit_err(diagnostics::BoxPatsRemoved { + span: box_span, + sugg_deref_macro_call: diagnostics::UseDerefMacro { + field: None, + before: before_span, + after: pat.span.shrink_to_hi(), + }, + sugg_removal: before_span, }); // Treat the box pattern like a deref pattern to avoid lots of "value not found" errors. Ok(PatKind::Deref(pat)) @@ -1905,12 +1912,13 @@ impl<'a> Parser<'a> { (pat, fieldname, false) } else { // FIXME: remove the recovery for parsing box patterrns entirely - let is_box = self.eat_keyword(exp!(Box)); - if is_box { - self.dcx() - .create_err(diagnostics::BoxPatternsRemoved { span: self.prev_token.span }) - .emit(); - } + let is_box = if self.eat_keyword(exp!(Box)) { + let span = self.prev_token.span; + self.dcx().span_delayed_bug(span, "box patterns have been removed"); + Some(span) + } else { + None + }; let boxed_span = self.token.span; let mutability = self.parse_mutability(); let by_ref = self.parse_byref(); @@ -1925,7 +1933,21 @@ impl<'a> Parser<'a> { ) { self.psess.gated_spans.gate(sym::mut_ref, fieldpat.span); } - let subpat = if is_box { + let subpat = if let Some(box_span) = is_box { + let prefix_span = box_span.until(boxed_span); + + self.dcx() + .create_err(diagnostics::BoxPatsRemoved { + span: box_span, + sugg_deref_macro_call: diagnostics::UseDerefMacro { + field: Some((prefix_span, fieldname)), + before: boxed_span.shrink_to_lo(), + after: hi.shrink_to_hi(), + }, + sugg_removal: prefix_span, + }) + .emit(); + self.mk_pat(lo.to(hi), PatKind::Deref(Box::new(fieldpat))) } else { fieldpat diff --git a/compiler/rustc_parse/src/parser/ty.rs b/compiler/rustc_parse/src/parser/ty.rs index 760b02622c678..31732882f7e86 100644 --- a/compiler/rustc_parse/src/parser/ty.rs +++ b/compiler/rustc_parse/src/parser/ty.rs @@ -1086,6 +1086,10 @@ impl<'a> Parser<'a> { /// Can the current token begin a bound? fn can_begin_bound(&mut self) -> bool { + // NOTE: Tokens `!`, `~`, `const` & `async` which represent the start of currently unstable + // trait bound modifiers are intentionally not included in `Token::can_begin_type` to + // avoid affecting stable macro matching behavior. + self.check_path() || self.check_lifetime() || self.check(exp!(Bang)) diff --git a/tests/ui/parser/box-can-begin-expr.rs b/tests/ui/parser/box-can-begin-expr.rs new file mode 100644 index 0000000000000..5f965d3f4e71d --- /dev/null +++ b/tests/ui/parser/box-can-begin-expr.rs @@ -0,0 +1,18 @@ +// Demonstrate that we still consider keyword `box` to begin expressions (`can_begin_expr`) even +// though we officially no longer support box expressions (#108471). +// It means that we take the first rule and fail immediately afterward. + +// FIXME: Remove `box` from the list of tokens that can begin expressions which would make us take +// the second rule instead and consequently accept this program (needs lang FCP). +// +// Alternatively we could unreserve keyword `box` (needs lang FCP) which would make us +// continue to take the first rule but also start accepting this program. + +macro_rules! mk { + ($e:expr) => {}; + (box $e:expr) => {}; +} + +mk!(box 0); //~ ERROR expected expression, found reserved keyword `box` + +fn main() {} diff --git a/tests/ui/parser/box-can-begin-expr.stderr b/tests/ui/parser/box-can-begin-expr.stderr new file mode 100644 index 0000000000000..304680c116fc0 --- /dev/null +++ b/tests/ui/parser/box-can-begin-expr.stderr @@ -0,0 +1,11 @@ +error: expected expression, found reserved keyword `box` + --> $DIR/box-can-begin-expr.rs:16:5 + | +LL | ($e:expr) => {}; + | ------- while parsing argument for this `expr` macro fragment +... +LL | mk!(box 0); + | ^^^ expected expression + +error: aborting due to 1 previous error + diff --git a/tests/ui/parser/removed-syntax/removed-syntax-box-patterns.rs b/tests/ui/parser/removed-syntax/removed-syntax-box-patterns.rs index 872f050a04034..13928aaf9886f 100644 --- a/tests/ui/parser/removed-syntax/removed-syntax-box-patterns.rs +++ b/tests/ui/parser/removed-syntax/removed-syntax-box-patterns.rs @@ -1,9 +1,11 @@ fn main() { - let box x = Box::new('c'); //~ ERROR `box_patterns` has been removed + let box x = Box::new('c'); //~ ERROR `box` patterns have been removed let _: char = x; struct Packet { x: Box } - let Packet { box x } = Packet { x: Box::new(0) }; //~ ERROR `box_patterns` has been removed + let Packet { box x } = Packet { x: Box::new(0) }; //~ ERROR `box` patterns have been removed let _: i32 = x; + + let Packet { box ref mut x }; //~ ERROR `box` patterns have been removed } diff --git a/tests/ui/parser/removed-syntax/removed-syntax-box-patterns.stderr b/tests/ui/parser/removed-syntax/removed-syntax-box-patterns.stderr index df35336ece5a9..2081098b699e2 100644 --- a/tests/ui/parser/removed-syntax/removed-syntax-box-patterns.stderr +++ b/tests/ui/parser/removed-syntax/removed-syntax-box-patterns.stderr @@ -1,14 +1,56 @@ -error: `box_patterns` has been removed +error: `box` patterns have been removed (feature `box_patterns`) --> $DIR/removed-syntax-box-patterns.rs:2:9 | LL | let box x = Box::new('c'); - | ^^^^^ + | ^^^ + | + = help: enable feature `deref_patterns` instead and... +help: ...if possible just remove keyword `box`... + | +LL - let box x = Box::new('c'); +LL + let x = Box::new('c'); + | +help: ...otherwise replace it with an invocation of macro `deref` + | +LL - let box x = Box::new('c'); +LL + let deref!(x) = Box::new('c'); + | -error: `box_patterns` has been removed +error: `box` patterns have been removed (feature `box_patterns`) --> $DIR/removed-syntax-box-patterns.rs:7:18 | LL | let Packet { box x } = Packet { x: Box::new(0) }; | ^^^ + | + = help: enable feature `deref_patterns` instead and... +help: ...if possible just remove keyword `box`... + | +LL - let Packet { box x } = Packet { x: Box::new(0) }; +LL + let Packet { x } = Packet { x: Box::new(0) }; + | +help: ...otherwise replace it with an invocation of macro `deref` + | +LL - let Packet { box x } = Packet { x: Box::new(0) }; +LL + let Packet { x: deref!(x) } = Packet { x: Box::new(0) }; + | + +error: `box` patterns have been removed (feature `box_patterns`) + --> $DIR/removed-syntax-box-patterns.rs:10:18 + | +LL | let Packet { box ref mut x }; + | ^^^ + | + = help: enable feature `deref_patterns` instead and... +help: ...if possible just remove keyword `box`... + | +LL - let Packet { box ref mut x }; +LL + let Packet { ref mut x }; + | +help: ...otherwise replace it with an invocation of macro `deref` + | +LL - let Packet { box ref mut x }; +LL + let Packet { x: deref!(ref mut x) }; + | -error: aborting due to 2 previous errors +error: aborting due to 3 previous errors diff --git a/tests/ui/parser/removed-syntax/removed-syntax-box.fixed b/tests/ui/parser/removed-syntax/removed-syntax-box.fixed deleted file mode 100644 index 8aec8c4cc435a..0000000000000 --- a/tests/ui/parser/removed-syntax/removed-syntax-box.fixed +++ /dev/null @@ -1,14 +0,0 @@ -//@ run-rustfix - -fn main() { - #[allow(dead_code)] - struct T { - a: u8, - b: u8, - } - let _ = Box::new(()); //~ ERROR `box_syntax` has been removed - let _ = Box::new(1); //~ ERROR `box_syntax` has been removed - let _ = Box::new(T { a: 12, b: 18 }); //~ ERROR `box_syntax` has been removed - let _ = Box::new([5; 30]); //~ ERROR `box_syntax` has been removed - let _: Box<()> = Box::new(()); //~ ERROR `box_syntax` has been removed -} diff --git a/tests/ui/parser/removed-syntax/removed-syntax-box.rs b/tests/ui/parser/removed-syntax/removed-syntax-box.rs deleted file mode 100644 index b77880e37553f..0000000000000 --- a/tests/ui/parser/removed-syntax/removed-syntax-box.rs +++ /dev/null @@ -1,14 +0,0 @@ -//@ run-rustfix - -fn main() { - #[allow(dead_code)] - struct T { - a: u8, - b: u8, - } - let _ = box (); //~ ERROR `box_syntax` has been removed - let _ = box 1; //~ ERROR `box_syntax` has been removed - let _ = box T { a: 12, b: 18 }; //~ ERROR `box_syntax` has been removed - let _ = box [5; 30]; //~ ERROR `box_syntax` has been removed - let _: Box<()> = box (); //~ ERROR `box_syntax` has been removed -} diff --git a/tests/ui/parser/removed-syntax/removed-syntax-box.stderr b/tests/ui/parser/removed-syntax/removed-syntax-box.stderr deleted file mode 100644 index 04e84a10fad68..0000000000000 --- a/tests/ui/parser/removed-syntax/removed-syntax-box.stderr +++ /dev/null @@ -1,62 +0,0 @@ -error: `box_syntax` has been removed - --> $DIR/removed-syntax-box.rs:9:13 - | -LL | let _ = box (); - | ^^^^^^ - | -help: use `Box::new()` instead - | -LL - let _ = box (); -LL + let _ = Box::new(()); - | - -error: `box_syntax` has been removed - --> $DIR/removed-syntax-box.rs:10:13 - | -LL | let _ = box 1; - | ^^^^^ - | -help: use `Box::new()` instead - | -LL - let _ = box 1; -LL + let _ = Box::new(1); - | - -error: `box_syntax` has been removed - --> $DIR/removed-syntax-box.rs:11:13 - | -LL | let _ = box T { a: 12, b: 18 }; - | ^^^^^^^^^^^^^^^^^^^^^^ - | -help: use `Box::new()` instead - | -LL - let _ = box T { a: 12, b: 18 }; -LL + let _ = Box::new(T { a: 12, b: 18 }); - | - -error: `box_syntax` has been removed - --> $DIR/removed-syntax-box.rs:12:13 - | -LL | let _ = box [5; 30]; - | ^^^^^^^^^^^ - | -help: use `Box::new()` instead - | -LL - let _ = box [5; 30]; -LL + let _ = Box::new([5; 30]); - | - -error: `box_syntax` has been removed - --> $DIR/removed-syntax-box.rs:13:22 - | -LL | let _: Box<()> = box (); - | ^^^^^^ - | -help: use `Box::new()` instead - | -LL - let _: Box<()> = box (); -LL + let _: Box<()> = Box::new(()); - | - -error: aborting due to 5 previous errors - diff --git a/tests/ui/parser/removed-syntax/removed-syntax-uniq-mut-expr.rs b/tests/ui/parser/removed-syntax/removed-syntax-uniq-mut-expr.rs deleted file mode 100644 index 08ef4b4326934..0000000000000 --- a/tests/ui/parser/removed-syntax/removed-syntax-uniq-mut-expr.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - let a_box = box mut 42; //~ ERROR expected expression, found keyword `mut` -} diff --git a/tests/ui/parser/removed-syntax/removed-syntax-uniq-mut-expr.stderr b/tests/ui/parser/removed-syntax/removed-syntax-uniq-mut-expr.stderr deleted file mode 100644 index 7aaedad19d86e..0000000000000 --- a/tests/ui/parser/removed-syntax/removed-syntax-uniq-mut-expr.stderr +++ /dev/null @@ -1,8 +0,0 @@ -error: expected expression, found keyword `mut` - --> $DIR/removed-syntax-uniq-mut-expr.rs:2:21 - | -LL | let a_box = box mut 42; - | ^^^ expected expression - -error: aborting due to 1 previous error -