diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index 0bbbd3a32c409..3ac226114cdb2 100644 --- a/compiler/rustc_parse/src/parser/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/diagnostics.rs @@ -17,7 +17,7 @@ use rustc_ast::{ }; use rustc_ast_pretty::pprust; use rustc_data_structures::fx::FxHashSet; -use rustc_errors::{pluralize, struct_span_err, Diagnostic, ErrorGuaranteed}; +use rustc_errors::{pluralize, struct_span_err, Diagnostic, EmissionGuarantee, ErrorGuaranteed}; use rustc_errors::{Applicability, DiagnosticBuilder, Handler, PResult}; use rustc_span::source_map::Spanned; use rustc_span::symbol::{kw, Ident}; @@ -156,6 +156,89 @@ impl AttemptLocalParseRecovery { } } +/// Information for emitting suggestions and recovering from +/// C-style `i++`, `--i`, etc. +#[derive(Debug, Copy, Clone)] +struct IncDecRecovery { + /// Is this increment/decrement its own statement? + standalone: IsStandalone, + /// Is this an increment or decrement? + op: IncOrDec, + /// Is this pre- or postfix? + fixity: UnaryFixity, +} + +/// Is an increment or decrement expression its own statement? +#[derive(Debug, Copy, Clone)] +enum IsStandalone { + /// It's standalone, i.e., its own statement. + Standalone, + /// It's a subexpression, i.e., *not* standalone. + Subexpr, + /// It's maybe standalone; we're not sure. + Maybe, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +enum IncOrDec { + Inc, + // FIXME: `i--` recovery isn't implemented yet + #[allow(dead_code)] + Dec, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +enum UnaryFixity { + Pre, + Post, +} + +impl IncOrDec { + fn chr(&self) -> char { + match self { + Self::Inc => '+', + Self::Dec => '-', + } + } + + fn name(&self) -> &'static str { + match self { + Self::Inc => "increment", + Self::Dec => "decrement", + } + } +} + +impl std::fmt::Display for UnaryFixity { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Pre => write!(f, "prefix"), + Self::Post => write!(f, "postfix"), + } + } +} + +struct MultiSugg { + msg: String, + patches: Vec<(Span, String)>, + applicability: Applicability, +} + +impl MultiSugg { + fn emit(self, err: &mut DiagnosticBuilder<'_, G>) { + err.multipart_suggestion(&self.msg, self.patches, self.applicability); + } + + /// Overrides individual messages and applicabilities. + fn emit_many( + err: &mut DiagnosticBuilder<'_, G>, + msg: &str, + applicability: Applicability, + suggestions: impl Iterator, + ) { + err.multipart_suggestions(msg, suggestions.map(|s| s.patches), applicability); + } +} // SnapshotParser is used to create a snapshot of the parser // without causing duplicate errors being emitted when the `Parser` // is dropped. @@ -1171,6 +1254,135 @@ impl<'a> Parser<'a> { Ok(()) } + pub(super) fn recover_from_prefix_increment( + &mut self, + operand_expr: P, + op_span: Span, + prev_is_semi: bool, + ) -> PResult<'a, P> { + let standalone = + if prev_is_semi { IsStandalone::Standalone } else { IsStandalone::Subexpr }; + let kind = IncDecRecovery { standalone, op: IncOrDec::Inc, fixity: UnaryFixity::Pre }; + + self.recover_from_inc_dec(operand_expr, kind, op_span) + } + + pub(super) fn recover_from_postfix_increment( + &mut self, + operand_expr: P, + op_span: Span, + ) -> PResult<'a, P> { + let kind = IncDecRecovery { + standalone: IsStandalone::Maybe, + op: IncOrDec::Inc, + fixity: UnaryFixity::Post, + }; + + self.recover_from_inc_dec(operand_expr, kind, op_span) + } + + fn recover_from_inc_dec( + &mut self, + base: P, + kind: IncDecRecovery, + op_span: Span, + ) -> PResult<'a, P> { + let mut err = self.struct_span_err( + op_span, + &format!("Rust has no {} {} operator", kind.fixity, kind.op.name()), + ); + err.span_label(op_span, &format!("not a valid {} operator", kind.fixity)); + + let help_base_case = |mut err: DiagnosticBuilder<'_, _>, base| { + err.help(&format!("use `{}= 1` instead", kind.op.chr())); + err.emit(); + Ok(base) + }; + + // (pre, post) + let spans = match kind.fixity { + UnaryFixity::Pre => (op_span, base.span.shrink_to_hi()), + UnaryFixity::Post => (base.span.shrink_to_lo(), op_span), + }; + + match kind.standalone { + IsStandalone::Standalone => self.inc_dec_standalone_suggest(kind, spans).emit(&mut err), + IsStandalone::Subexpr => { + let Ok(base_src) = self.span_to_snippet(base.span) + else { return help_base_case(err, base) }; + match kind.fixity { + UnaryFixity::Pre => { + self.prefix_inc_dec_suggest(base_src, kind, spans).emit(&mut err) + } + UnaryFixity::Post => { + self.postfix_inc_dec_suggest(base_src, kind, spans).emit(&mut err) + } + } + } + IsStandalone::Maybe => { + let Ok(base_src) = self.span_to_snippet(base.span) + else { return help_base_case(err, base) }; + let sugg1 = match kind.fixity { + UnaryFixity::Pre => self.prefix_inc_dec_suggest(base_src, kind, spans), + UnaryFixity::Post => self.postfix_inc_dec_suggest(base_src, kind, spans), + }; + let sugg2 = self.inc_dec_standalone_suggest(kind, spans); + MultiSugg::emit_many( + &mut err, + "use `+= 1` instead", + Applicability::Unspecified, + [sugg1, sugg2].into_iter(), + ) + } + } + Err(err) + } + + fn prefix_inc_dec_suggest( + &mut self, + base_src: String, + kind: IncDecRecovery, + (pre_span, post_span): (Span, Span), + ) -> MultiSugg { + MultiSugg { + msg: format!("use `{}= 1` instead", kind.op.chr()), + patches: vec![ + (pre_span, "{ ".to_string()), + (post_span, format!(" {}= 1; {} }}", kind.op.chr(), base_src)), + ], + applicability: Applicability::MachineApplicable, + } + } + + fn postfix_inc_dec_suggest( + &mut self, + base_src: String, + kind: IncDecRecovery, + (pre_span, post_span): (Span, Span), + ) -> MultiSugg { + let tmp_var = if base_src.trim() == "tmp" { "tmp_" } else { "tmp" }; + MultiSugg { + msg: format!("use `{}= 1` instead", kind.op.chr()), + patches: vec![ + (pre_span, format!("{{ let {} = ", tmp_var)), + (post_span, format!("; {} {}= 1; {} }}", base_src, kind.op.chr(), tmp_var)), + ], + applicability: Applicability::HasPlaceholders, + } + } + + fn inc_dec_standalone_suggest( + &mut self, + kind: IncDecRecovery, + (pre_span, post_span): (Span, Span), + ) -> MultiSugg { + MultiSugg { + msg: format!("use `{}= 1` instead", kind.op.chr()), + patches: vec![(pre_span, String::new()), (post_span, format!(" {}= 1", kind.op.chr()))], + applicability: Applicability::MachineApplicable, + } + } + /// Tries to recover from associated item paths like `[T]::AssocItem` / `(T, U)::AssocItem`. /// Attempts to convert the base expression/pattern/type into a type, parses the `::AssocItem` /// tail, and combines them into a `::AssocItem` expression/pattern/type. diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 20d035fba195a..7c407cbe465f8 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -267,6 +267,17 @@ impl<'a> Parser<'a> { self.bump(); } + if self.prev_token == token::BinOp(token::Plus) + && self.token == token::BinOp(token::Plus) + && self.prev_token.span.between(self.token.span).is_empty() + { + let op_span = self.prev_token.span.to(self.token.span); + // Eat the second `+` + self.bump(); + lhs = self.recover_from_postfix_increment(lhs, op_span)?; + continue; + } + let op = op.node; // Special cases: if op == AssocOp::As { @@ -580,6 +591,19 @@ impl<'a> Parser<'a> { this.bump(); this.parse_prefix_expr(None) } // `+expr` + // Recover from `++x`: + token::BinOp(token::Plus) + if this.look_ahead(1, |t| *t == token::BinOp(token::Plus)) => + { + let prev_is_semi = this.prev_token == token::Semi; + let pre_span = this.token.span.to(this.look_ahead(1, |t| t.span)); + // Eat both `+`s. + this.bump(); + this.bump(); + + let operand_expr = this.parse_dot_or_call_expr(Default::default())?; + this.recover_from_prefix_increment(operand_expr, pre_span, prev_is_semi) + } token::Ident(..) if this.token.is_keyword(kw::Box) => { make_it!(this, attrs, |this, _| this.parse_box_expr(lo)) } diff --git a/src/test/ui/parser/increment-autofix.fixed b/src/test/ui/parser/increment-autofix.fixed new file mode 100644 index 0000000000000..7a426badfc2d3 --- /dev/null +++ b/src/test/ui/parser/increment-autofix.fixed @@ -0,0 +1,31 @@ +// run-rustfix + +pub fn pre_regular() { + let mut i = 0; + i += 1; //~ ERROR Rust has no prefix increment operator + println!("{}", i); +} + +pub fn pre_while() { + let mut i = 0; + while { i += 1; i } < 5 { + //~^ ERROR Rust has no prefix increment operator + println!("{}", i); + } +} + +pub fn pre_regular_tmp() { + let mut tmp = 0; + tmp += 1; //~ ERROR Rust has no prefix increment operator + println!("{}", tmp); +} + +pub fn pre_while_tmp() { + let mut tmp = 0; + while { tmp += 1; tmp } < 5 { + //~^ ERROR Rust has no prefix increment operator + println!("{}", tmp); + } +} + +fn main() {} diff --git a/src/test/ui/parser/increment-autofix.rs b/src/test/ui/parser/increment-autofix.rs new file mode 100644 index 0000000000000..d38603697a7a6 --- /dev/null +++ b/src/test/ui/parser/increment-autofix.rs @@ -0,0 +1,31 @@ +// run-rustfix + +pub fn pre_regular() { + let mut i = 0; + ++i; //~ ERROR Rust has no prefix increment operator + println!("{}", i); +} + +pub fn pre_while() { + let mut i = 0; + while ++i < 5 { + //~^ ERROR Rust has no prefix increment operator + println!("{}", i); + } +} + +pub fn pre_regular_tmp() { + let mut tmp = 0; + ++tmp; //~ ERROR Rust has no prefix increment operator + println!("{}", tmp); +} + +pub fn pre_while_tmp() { + let mut tmp = 0; + while ++tmp < 5 { + //~^ ERROR Rust has no prefix increment operator + println!("{}", tmp); + } +} + +fn main() {} diff --git a/src/test/ui/parser/increment-autofix.stderr b/src/test/ui/parser/increment-autofix.stderr new file mode 100644 index 0000000000000..593592ba4ab74 --- /dev/null +++ b/src/test/ui/parser/increment-autofix.stderr @@ -0,0 +1,52 @@ +error: Rust has no prefix increment operator + --> $DIR/increment-autofix.rs:5:5 + | +LL | ++i; + | ^^ not a valid prefix operator + | +help: use `+= 1` instead + | +LL - ++i; +LL + i += 1; + | + +error: Rust has no prefix increment operator + --> $DIR/increment-autofix.rs:11:11 + | +LL | while ++i < 5 { + | ----- ^^ not a valid prefix operator + | | + | while parsing the condition of this `while` expression + | +help: use `+= 1` instead + | +LL | while { i += 1; i } < 5 { + | ~ +++++++++ + +error: Rust has no prefix increment operator + --> $DIR/increment-autofix.rs:19:5 + | +LL | ++tmp; + | ^^ not a valid prefix operator + | +help: use `+= 1` instead + | +LL - ++tmp; +LL + tmp += 1; + | + +error: Rust has no prefix increment operator + --> $DIR/increment-autofix.rs:25:11 + | +LL | while ++tmp < 5 { + | ----- ^^ not a valid prefix operator + | | + | while parsing the condition of this `while` expression + | +help: use `+= 1` instead + | +LL | while { tmp += 1; tmp } < 5 { + | ~ +++++++++++ + +error: aborting due to 4 previous errors + diff --git a/src/test/ui/parser/increment-notfixed.rs b/src/test/ui/parser/increment-notfixed.rs new file mode 100644 index 0000000000000..15f159e53d294 --- /dev/null +++ b/src/test/ui/parser/increment-notfixed.rs @@ -0,0 +1,61 @@ +struct Foo { + bar: Bar, +} + +struct Bar { + qux: i32, +} + +pub fn post_regular() { + let mut i = 0; + i++; //~ ERROR Rust has no postfix increment operator + println!("{}", i); +} + +pub fn post_while() { + let mut i = 0; + while i++ < 5 { + //~^ ERROR Rust has no postfix increment operator + println!("{}", i); + } +} + +pub fn post_regular_tmp() { + let mut tmp = 0; + tmp++; //~ ERROR Rust has no postfix increment operator + println!("{}", tmp); +} + +pub fn post_while_tmp() { + let mut tmp = 0; + while tmp++ < 5 { + //~^ ERROR Rust has no postfix increment operator + println!("{}", tmp); + } +} + +pub fn post_field() { + let foo = Foo { bar: Bar { qux: 0 } }; + foo.bar.qux++; + //~^ ERROR Rust has no postfix increment operator + println!("{}", foo.bar.qux); +} + +pub fn post_field_tmp() { + struct S { + tmp: i32 + } + let s = S { tmp: 0 }; + s.tmp++; + //~^ ERROR Rust has no postfix increment operator + println!("{}", s.tmp); +} + +pub fn pre_field() { + let foo = Foo { bar: Bar { qux: 0 } }; + ++foo.bar.qux; + //~^ ERROR Rust has no prefix increment operator + println!("{}", foo.bar.qux); +} + +fn main() {} diff --git a/src/test/ui/parser/increment-notfixed.stderr b/src/test/ui/parser/increment-notfixed.stderr new file mode 100644 index 0000000000000..f23595da32ac8 --- /dev/null +++ b/src/test/ui/parser/increment-notfixed.stderr @@ -0,0 +1,102 @@ +error: Rust has no postfix increment operator + --> $DIR/increment-notfixed.rs:11:6 + | +LL | i++; + | ^^ not a valid postfix operator + | +help: use `+= 1` instead + | +LL | { let tmp = i; i += 1; tmp }; + | +++++++++++ ~~~~~~~~~~~~~~~ +LL - i++; +LL + i += 1; + | + +error: Rust has no postfix increment operator + --> $DIR/increment-notfixed.rs:17:12 + | +LL | while i++ < 5 { + | ----- ^^ not a valid postfix operator + | | + | while parsing the condition of this `while` expression + | +help: use `+= 1` instead + | +LL | while { let tmp = i; i += 1; tmp } < 5 { + | +++++++++++ ~~~~~~~~~~~~~~~ +LL - while i++ < 5 { +LL + while i += 1 < 5 { + | + +error: Rust has no postfix increment operator + --> $DIR/increment-notfixed.rs:25:8 + | +LL | tmp++; + | ^^ not a valid postfix operator + | +help: use `+= 1` instead + | +LL | { let tmp_ = tmp; tmp += 1; tmp_ }; + | ++++++++++++ ~~~~~~~~~~~~~~~~~~ +LL - tmp++; +LL + tmp += 1; + | + +error: Rust has no postfix increment operator + --> $DIR/increment-notfixed.rs:31:14 + | +LL | while tmp++ < 5 { + | ----- ^^ not a valid postfix operator + | | + | while parsing the condition of this `while` expression + | +help: use `+= 1` instead + | +LL | while { let tmp_ = tmp; tmp += 1; tmp_ } < 5 { + | ++++++++++++ ~~~~~~~~~~~~~~~~~~ +LL - while tmp++ < 5 { +LL + while tmp += 1 < 5 { + | + +error: Rust has no postfix increment operator + --> $DIR/increment-notfixed.rs:39:16 + | +LL | foo.bar.qux++; + | ^^ not a valid postfix operator + | +help: use `+= 1` instead + | +LL | { let tmp = foo.bar.qux; foo.bar.qux += 1; tmp }; + | +++++++++++ ~~~~~~~~~~~~~~~~~~~~~~~~~ +LL - foo.bar.qux++; +LL + foo.bar.qux += 1; + | + +error: Rust has no postfix increment operator + --> $DIR/increment-notfixed.rs:49:10 + | +LL | s.tmp++; + | ^^ not a valid postfix operator + | +help: use `+= 1` instead + | +LL | { let tmp = s.tmp; s.tmp += 1; tmp }; + | +++++++++++ ~~~~~~~~~~~~~~~~~~~ +LL - s.tmp++; +LL + s.tmp += 1; + | + +error: Rust has no prefix increment operator + --> $DIR/increment-notfixed.rs:56:5 + | +LL | ++foo.bar.qux; + | ^^ not a valid prefix operator + | +help: use `+= 1` instead + | +LL - ++foo.bar.qux; +LL + foo.bar.qux += 1; + | + +error: aborting due to 7 previous errors +