Skip to content

Commit 133859d

Browse files
committed
Auto merge of #88672 - camelid:inc-parser-sugg, r=davidtwco
Suggest `i += 1` when we see `i++` or `++i` Closes #83502 (for `i++` and `++i`; `--i` should be covered by #82987, and `i--` is tricky to handle). This is a continuation of #83536. r? `@estebank`
2 parents c1550e3 + 4943688 commit 133859d

File tree

7 files changed

+514
-1
lines changed

7 files changed

+514
-1
lines changed

compiler/rustc_parse/src/parser/diagnostics.rs

+213-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use rustc_ast::{
1717
};
1818
use rustc_ast_pretty::pprust;
1919
use rustc_data_structures::fx::FxHashSet;
20-
use rustc_errors::{pluralize, struct_span_err, Diagnostic, ErrorGuaranteed};
20+
use rustc_errors::{pluralize, struct_span_err, Diagnostic, EmissionGuarantee, ErrorGuaranteed};
2121
use rustc_errors::{Applicability, DiagnosticBuilder, Handler, PResult};
2222
use rustc_span::source_map::Spanned;
2323
use rustc_span::symbol::{kw, Ident};
@@ -156,6 +156,89 @@ impl AttemptLocalParseRecovery {
156156
}
157157
}
158158

159+
/// Information for emitting suggestions and recovering from
160+
/// C-style `i++`, `--i`, etc.
161+
#[derive(Debug, Copy, Clone)]
162+
struct IncDecRecovery {
163+
/// Is this increment/decrement its own statement?
164+
standalone: IsStandalone,
165+
/// Is this an increment or decrement?
166+
op: IncOrDec,
167+
/// Is this pre- or postfix?
168+
fixity: UnaryFixity,
169+
}
170+
171+
/// Is an increment or decrement expression its own statement?
172+
#[derive(Debug, Copy, Clone)]
173+
enum IsStandalone {
174+
/// It's standalone, i.e., its own statement.
175+
Standalone,
176+
/// It's a subexpression, i.e., *not* standalone.
177+
Subexpr,
178+
/// It's maybe standalone; we're not sure.
179+
Maybe,
180+
}
181+
182+
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
183+
enum IncOrDec {
184+
Inc,
185+
// FIXME: `i--` recovery isn't implemented yet
186+
#[allow(dead_code)]
187+
Dec,
188+
}
189+
190+
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
191+
enum UnaryFixity {
192+
Pre,
193+
Post,
194+
}
195+
196+
impl IncOrDec {
197+
fn chr(&self) -> char {
198+
match self {
199+
Self::Inc => '+',
200+
Self::Dec => '-',
201+
}
202+
}
203+
204+
fn name(&self) -> &'static str {
205+
match self {
206+
Self::Inc => "increment",
207+
Self::Dec => "decrement",
208+
}
209+
}
210+
}
211+
212+
impl std::fmt::Display for UnaryFixity {
213+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
214+
match self {
215+
Self::Pre => write!(f, "prefix"),
216+
Self::Post => write!(f, "postfix"),
217+
}
218+
}
219+
}
220+
221+
struct MultiSugg {
222+
msg: String,
223+
patches: Vec<(Span, String)>,
224+
applicability: Applicability,
225+
}
226+
227+
impl MultiSugg {
228+
fn emit<G: EmissionGuarantee>(self, err: &mut DiagnosticBuilder<'_, G>) {
229+
err.multipart_suggestion(&self.msg, self.patches, self.applicability);
230+
}
231+
232+
/// Overrides individual messages and applicabilities.
233+
fn emit_many<G: EmissionGuarantee>(
234+
err: &mut DiagnosticBuilder<'_, G>,
235+
msg: &str,
236+
applicability: Applicability,
237+
suggestions: impl Iterator<Item = Self>,
238+
) {
239+
err.multipart_suggestions(msg, suggestions.map(|s| s.patches), applicability);
240+
}
241+
}
159242
// SnapshotParser is used to create a snapshot of the parser
160243
// without causing duplicate errors being emitted when the `Parser`
161244
// is dropped.
@@ -1171,6 +1254,135 @@ impl<'a> Parser<'a> {
11711254
Ok(())
11721255
}
11731256

1257+
pub(super) fn recover_from_prefix_increment(
1258+
&mut self,
1259+
operand_expr: P<Expr>,
1260+
op_span: Span,
1261+
prev_is_semi: bool,
1262+
) -> PResult<'a, P<Expr>> {
1263+
let standalone =
1264+
if prev_is_semi { IsStandalone::Standalone } else { IsStandalone::Subexpr };
1265+
let kind = IncDecRecovery { standalone, op: IncOrDec::Inc, fixity: UnaryFixity::Pre };
1266+
1267+
self.recover_from_inc_dec(operand_expr, kind, op_span)
1268+
}
1269+
1270+
pub(super) fn recover_from_postfix_increment(
1271+
&mut self,
1272+
operand_expr: P<Expr>,
1273+
op_span: Span,
1274+
) -> PResult<'a, P<Expr>> {
1275+
let kind = IncDecRecovery {
1276+
standalone: IsStandalone::Maybe,
1277+
op: IncOrDec::Inc,
1278+
fixity: UnaryFixity::Post,
1279+
};
1280+
1281+
self.recover_from_inc_dec(operand_expr, kind, op_span)
1282+
}
1283+
1284+
fn recover_from_inc_dec(
1285+
&mut self,
1286+
base: P<Expr>,
1287+
kind: IncDecRecovery,
1288+
op_span: Span,
1289+
) -> PResult<'a, P<Expr>> {
1290+
let mut err = self.struct_span_err(
1291+
op_span,
1292+
&format!("Rust has no {} {} operator", kind.fixity, kind.op.name()),
1293+
);
1294+
err.span_label(op_span, &format!("not a valid {} operator", kind.fixity));
1295+
1296+
let help_base_case = |mut err: DiagnosticBuilder<'_, _>, base| {
1297+
err.help(&format!("use `{}= 1` instead", kind.op.chr()));
1298+
err.emit();
1299+
Ok(base)
1300+
};
1301+
1302+
// (pre, post)
1303+
let spans = match kind.fixity {
1304+
UnaryFixity::Pre => (op_span, base.span.shrink_to_hi()),
1305+
UnaryFixity::Post => (base.span.shrink_to_lo(), op_span),
1306+
};
1307+
1308+
match kind.standalone {
1309+
IsStandalone::Standalone => self.inc_dec_standalone_suggest(kind, spans).emit(&mut err),
1310+
IsStandalone::Subexpr => {
1311+
let Ok(base_src) = self.span_to_snippet(base.span)
1312+
else { return help_base_case(err, base) };
1313+
match kind.fixity {
1314+
UnaryFixity::Pre => {
1315+
self.prefix_inc_dec_suggest(base_src, kind, spans).emit(&mut err)
1316+
}
1317+
UnaryFixity::Post => {
1318+
self.postfix_inc_dec_suggest(base_src, kind, spans).emit(&mut err)
1319+
}
1320+
}
1321+
}
1322+
IsStandalone::Maybe => {
1323+
let Ok(base_src) = self.span_to_snippet(base.span)
1324+
else { return help_base_case(err, base) };
1325+
let sugg1 = match kind.fixity {
1326+
UnaryFixity::Pre => self.prefix_inc_dec_suggest(base_src, kind, spans),
1327+
UnaryFixity::Post => self.postfix_inc_dec_suggest(base_src, kind, spans),
1328+
};
1329+
let sugg2 = self.inc_dec_standalone_suggest(kind, spans);
1330+
MultiSugg::emit_many(
1331+
&mut err,
1332+
"use `+= 1` instead",
1333+
Applicability::Unspecified,
1334+
[sugg1, sugg2].into_iter(),
1335+
)
1336+
}
1337+
}
1338+
Err(err)
1339+
}
1340+
1341+
fn prefix_inc_dec_suggest(
1342+
&mut self,
1343+
base_src: String,
1344+
kind: IncDecRecovery,
1345+
(pre_span, post_span): (Span, Span),
1346+
) -> MultiSugg {
1347+
MultiSugg {
1348+
msg: format!("use `{}= 1` instead", kind.op.chr()),
1349+
patches: vec![
1350+
(pre_span, "{ ".to_string()),
1351+
(post_span, format!(" {}= 1; {} }}", kind.op.chr(), base_src)),
1352+
],
1353+
applicability: Applicability::MachineApplicable,
1354+
}
1355+
}
1356+
1357+
fn postfix_inc_dec_suggest(
1358+
&mut self,
1359+
base_src: String,
1360+
kind: IncDecRecovery,
1361+
(pre_span, post_span): (Span, Span),
1362+
) -> MultiSugg {
1363+
let tmp_var = if base_src.trim() == "tmp" { "tmp_" } else { "tmp" };
1364+
MultiSugg {
1365+
msg: format!("use `{}= 1` instead", kind.op.chr()),
1366+
patches: vec![
1367+
(pre_span, format!("{{ let {} = ", tmp_var)),
1368+
(post_span, format!("; {} {}= 1; {} }}", base_src, kind.op.chr(), tmp_var)),
1369+
],
1370+
applicability: Applicability::HasPlaceholders,
1371+
}
1372+
}
1373+
1374+
fn inc_dec_standalone_suggest(
1375+
&mut self,
1376+
kind: IncDecRecovery,
1377+
(pre_span, post_span): (Span, Span),
1378+
) -> MultiSugg {
1379+
MultiSugg {
1380+
msg: format!("use `{}= 1` instead", kind.op.chr()),
1381+
patches: vec![(pre_span, String::new()), (post_span, format!(" {}= 1", kind.op.chr()))],
1382+
applicability: Applicability::MachineApplicable,
1383+
}
1384+
}
1385+
11741386
/// Tries to recover from associated item paths like `[T]::AssocItem` / `(T, U)::AssocItem`.
11751387
/// Attempts to convert the base expression/pattern/type into a type, parses the `::AssocItem`
11761388
/// tail, and combines them into a `<Ty>::AssocItem` expression/pattern/type.

compiler/rustc_parse/src/parser/expr.rs

+24
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,17 @@ impl<'a> Parser<'a> {
267267
self.bump();
268268
}
269269

270+
if self.prev_token == token::BinOp(token::Plus)
271+
&& self.token == token::BinOp(token::Plus)
272+
&& self.prev_token.span.between(self.token.span).is_empty()
273+
{
274+
let op_span = self.prev_token.span.to(self.token.span);
275+
// Eat the second `+`
276+
self.bump();
277+
lhs = self.recover_from_postfix_increment(lhs, op_span)?;
278+
continue;
279+
}
280+
270281
let op = op.node;
271282
// Special cases:
272283
if op == AssocOp::As {
@@ -580,6 +591,19 @@ impl<'a> Parser<'a> {
580591
this.bump();
581592
this.parse_prefix_expr(None)
582593
} // `+expr`
594+
// Recover from `++x`:
595+
token::BinOp(token::Plus)
596+
if this.look_ahead(1, |t| *t == token::BinOp(token::Plus)) =>
597+
{
598+
let prev_is_semi = this.prev_token == token::Semi;
599+
let pre_span = this.token.span.to(this.look_ahead(1, |t| t.span));
600+
// Eat both `+`s.
601+
this.bump();
602+
this.bump();
603+
604+
let operand_expr = this.parse_dot_or_call_expr(Default::default())?;
605+
this.recover_from_prefix_increment(operand_expr, pre_span, prev_is_semi)
606+
}
583607
token::Ident(..) if this.token.is_keyword(kw::Box) => {
584608
make_it!(this, attrs, |this, _| this.parse_box_expr(lo))
585609
}
+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// run-rustfix
2+
3+
pub fn pre_regular() {
4+
let mut i = 0;
5+
i += 1; //~ ERROR Rust has no prefix increment operator
6+
println!("{}", i);
7+
}
8+
9+
pub fn pre_while() {
10+
let mut i = 0;
11+
while { i += 1; i } < 5 {
12+
//~^ ERROR Rust has no prefix increment operator
13+
println!("{}", i);
14+
}
15+
}
16+
17+
pub fn pre_regular_tmp() {
18+
let mut tmp = 0;
19+
tmp += 1; //~ ERROR Rust has no prefix increment operator
20+
println!("{}", tmp);
21+
}
22+
23+
pub fn pre_while_tmp() {
24+
let mut tmp = 0;
25+
while { tmp += 1; tmp } < 5 {
26+
//~^ ERROR Rust has no prefix increment operator
27+
println!("{}", tmp);
28+
}
29+
}
30+
31+
fn main() {}
+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// run-rustfix
2+
3+
pub fn pre_regular() {
4+
let mut i = 0;
5+
++i; //~ ERROR Rust has no prefix increment operator
6+
println!("{}", i);
7+
}
8+
9+
pub fn pre_while() {
10+
let mut i = 0;
11+
while ++i < 5 {
12+
//~^ ERROR Rust has no prefix increment operator
13+
println!("{}", i);
14+
}
15+
}
16+
17+
pub fn pre_regular_tmp() {
18+
let mut tmp = 0;
19+
++tmp; //~ ERROR Rust has no prefix increment operator
20+
println!("{}", tmp);
21+
}
22+
23+
pub fn pre_while_tmp() {
24+
let mut tmp = 0;
25+
while ++tmp < 5 {
26+
//~^ ERROR Rust has no prefix increment operator
27+
println!("{}", tmp);
28+
}
29+
}
30+
31+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
error: Rust has no prefix increment operator
2+
--> $DIR/increment-autofix.rs:5:5
3+
|
4+
LL | ++i;
5+
| ^^ not a valid prefix operator
6+
|
7+
help: use `+= 1` instead
8+
|
9+
LL - ++i;
10+
LL + i += 1;
11+
|
12+
13+
error: Rust has no prefix increment operator
14+
--> $DIR/increment-autofix.rs:11:11
15+
|
16+
LL | while ++i < 5 {
17+
| ----- ^^ not a valid prefix operator
18+
| |
19+
| while parsing the condition of this `while` expression
20+
|
21+
help: use `+= 1` instead
22+
|
23+
LL | while { i += 1; i } < 5 {
24+
| ~ +++++++++
25+
26+
error: Rust has no prefix increment operator
27+
--> $DIR/increment-autofix.rs:19:5
28+
|
29+
LL | ++tmp;
30+
| ^^ not a valid prefix operator
31+
|
32+
help: use `+= 1` instead
33+
|
34+
LL - ++tmp;
35+
LL + tmp += 1;
36+
|
37+
38+
error: Rust has no prefix increment operator
39+
--> $DIR/increment-autofix.rs:25:11
40+
|
41+
LL | while ++tmp < 5 {
42+
| ----- ^^ not a valid prefix operator
43+
| |
44+
| while parsing the condition of this `while` expression
45+
|
46+
help: use `+= 1` instead
47+
|
48+
LL | while { tmp += 1; tmp } < 5 {
49+
| ~ +++++++++++
50+
51+
error: aborting due to 4 previous errors
52+

0 commit comments

Comments
 (0)