Skip to content

Commit 00aeabc

Browse files
committed
Recover from some const expr parse errors
1 parent 892eac1 commit 00aeabc

File tree

8 files changed

+187
-23
lines changed

8 files changed

+187
-23
lines changed

src/librustc_ast/ast.rs

+9
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,15 @@ pub enum AngleBracketedArg {
228228
Constraint(AssocTyConstraint),
229229
}
230230

231+
impl AngleBracketedArg {
232+
pub fn span(&self) -> Span {
233+
match self {
234+
AngleBracketedArg::Arg(arg) => arg.span(),
235+
AngleBracketedArg::Constraint(constraint) => constraint.span,
236+
}
237+
}
238+
}
239+
231240
impl Into<Option<P<GenericArgs>>> for AngleBracketedArgs {
232241
fn into(self) -> Option<P<GenericArgs>> {
233242
Some(P(GenericArgs::AngleBracketed(self)))

src/librustc_ast/token.rs

+7
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,13 @@ impl TokenKind {
313313
_ => None,
314314
}
315315
}
316+
317+
pub fn should_end_const_arg(&self) -> bool {
318+
match self {
319+
Gt | Ge | BinOp(Shr) | BinOpEq(Shr) => true,
320+
_ => false,
321+
}
322+
}
316323
}
317324

318325
impl Token {

src/librustc_parse/parser/expr.rs

+11-5
Original file line numberDiff line numberDiff line change
@@ -329,12 +329,18 @@ impl<'a> Parser<'a> {
329329
/// The method does not advance the current token.
330330
///
331331
/// Also performs recovery for `and` / `or` which are mistaken for `&&` and `||` respectively.
332-
fn check_assoc_op(&self) -> Option<Spanned<AssocOp>> {
332+
crate fn check_assoc_op(&self) -> Option<Spanned<AssocOp>> {
333333
let (op, span) = match (AssocOp::from_token(&self.token), self.token.ident()) {
334-
// When parsing const expressions, stop parsing when encountering `<` and `>`.
335-
(Some(AssocOp::ShiftRight), _) | (Some(AssocOp::Greater), _)
336-
if self.restrictions.contains(Restrictions::CONST_EXPR) =>
337-
{
334+
// When parsing const expressions, stop parsing when encountering `>`.
335+
(
336+
Some(
337+
AssocOp::ShiftRight
338+
| AssocOp::Greater
339+
| AssocOp::GreaterEqual
340+
| AssocOp::AssignOp(token::BinOpToken::Shr),
341+
),
342+
_,
343+
) if self.restrictions.contains(Restrictions::CONST_EXPR) => {
338344
return None;
339345
}
340346
(Some(op), _) => (op, self.token.span),

src/librustc_parse/parser/path.rs

+108-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ use rustc_ast::ast::{AnonConst, AssocTyConstraint, AssocTyConstraintKind, BlockC
66
use rustc_ast::ast::{Ident, Path, PathSegment, QSelf};
77
use rustc_ast::ptr::P;
88
use rustc_ast::token::{self, Token};
9-
use rustc_errors::{pluralize, Applicability, PResult};
9+
use rustc_ast::util::parser::AssocOp;
10+
use rustc_errors::{pluralize, Applicability, DiagnosticBuilder, PResult};
1011
use rustc_span::source_map::{BytePos, Span};
1112
use rustc_span::symbol::{kw, sym};
1213

@@ -392,12 +393,110 @@ impl<'a> Parser<'a> {
392393
while let Some(arg) = self.parse_angle_arg()? {
393394
args.push(arg);
394395
if !self.eat(&token::Comma) {
396+
if self.token.kind.should_end_const_arg() {
397+
// We will correctly parse a closing `>`, exit.
398+
} else {
399+
// Try to recover from possible `const` arg without braces.
400+
let arg = args.pop().unwrap();
401+
// FIXME: for some reason using `unexpected` or `expected_one_of_not_found` has
402+
// adverse side-effects to subsequent errors and seems to advance the parser.
403+
// We are causing this error here exclusively in case that a `const` expression
404+
// could be recovered from the current parser state, even if followed by more
405+
// arguments after a comma.
406+
let mut err = self.struct_span_err(
407+
self.token.span,
408+
&format!(
409+
"expected one of `,` or `>`, found {}",
410+
super::token_descr(&self.token)
411+
),
412+
);
413+
err.span_label(self.token.span, "expected one of `,` or `>`");
414+
match self.recover_const_arg(arg.span(), err) {
415+
Ok(arg) => {
416+
args.push(AngleBracketedArg::Arg(arg));
417+
if self.eat(&token::Comma) {
418+
continue;
419+
}
420+
}
421+
Err(mut err) => {
422+
args.push(arg);
423+
// We will emit a more generic error later.
424+
err.delay_as_bug();
425+
}
426+
}
427+
}
395428
break;
396429
}
397430
}
398431
Ok(args)
399432
}
400433

434+
/// Try to recover from possible `const` arg without braces.
435+
///
436+
/// When encountering code like `foo::< bar + 3 >` or `foo::< bar - baz >` we suggest
437+
/// `foo::<{ bar + 3 }>` and `foo::<{ bar - baz }>` respectively. We only provide a suggestion
438+
/// when we have a high degree of certainty that the resulting expression would be well formed.
439+
pub fn recover_const_arg(
440+
&mut self,
441+
start: Span,
442+
mut err: DiagnosticBuilder<'a>,
443+
) -> PResult<'a, GenericArg> {
444+
let is_op = AssocOp::from_token(&self.token)
445+
.and_then(|op| {
446+
if let AssocOp::Greater
447+
| AssocOp::Less
448+
| AssocOp::ShiftRight
449+
| AssocOp::GreaterEqual
450+
| AssocOp::Assign // Don't recover from `foo::<bar = baz>`
451+
| AssocOp::AssignOp(_) = op
452+
{
453+
None
454+
} else {
455+
Some(op)
456+
}
457+
})
458+
.is_some();
459+
// This will be true when a trait object type `Foo +` has been parsed.
460+
let was_op = self.prev_token.kind == token::BinOp(token::Plus);
461+
if !is_op && !was_op {
462+
// We perform these checks and early return to avoid taking a snapshot unnecessarily.
463+
return Err(err);
464+
}
465+
let snapshot = self.clone();
466+
if is_op {
467+
self.bump();
468+
}
469+
match self.parse_expr_res(Restrictions::CONST_EXPR, None) {
470+
Ok(expr) => {
471+
if token::Comma == self.token.kind || self.token.kind.should_end_const_arg() {
472+
// Avoid the following output by checking that we consumed a full const arg:
473+
// help: to write a `const` expression, surround it with braces for it to
474+
// be unambiguous
475+
// |
476+
// LL | let sr: Vec<{ (u32, _, _) = vec![] };
477+
// | ^ ^
478+
err.multipart_suggestion(
479+
"to write a `const` expression, surround it with braces for it to be \
480+
unambiguous",
481+
vec![
482+
(start.shrink_to_lo(), "{ ".to_string()),
483+
(expr.span.shrink_to_hi(), " }".to_string()),
484+
],
485+
Applicability::MaybeIncorrect,
486+
);
487+
let value = self.mk_expr_err(start.to(expr.span));
488+
err.emit();
489+
return Ok(GenericArg::Const(AnonConst { id: ast::DUMMY_NODE_ID, value }));
490+
}
491+
}
492+
Err(mut err) => {
493+
err.cancel();
494+
}
495+
}
496+
*self = snapshot;
497+
Err(err)
498+
}
499+
401500
/// Parses a single argument in the angle arguments `<...>` of a path segment.
402501
fn parse_angle_arg(&mut self) -> PResult<'a, Option<AngleBracketedArg>> {
403502
if self.check_ident() && self.look_ahead(1, |t| matches!(t.kind, token::Eq | token::Colon))
@@ -474,6 +573,7 @@ impl<'a> Parser<'a> {
474573
/// Parse a generic argument in a path segment.
475574
/// This does not include constraints, e.g., `Item = u8`, which is handled in `parse_angle_arg`.
476575
fn parse_generic_arg(&mut self) -> PResult<'a, Option<GenericArg>> {
576+
let start = self.token.span;
477577
let arg = if self.check_lifetime() && self.look_ahead(1, |t| !t.is_like_plus()) {
478578
// Parse lifetime argument.
479579
GenericArg::Lifetime(self.expect_lifetime())
@@ -502,7 +602,13 @@ impl<'a> Parser<'a> {
502602
GenericArg::Const(AnonConst { id: ast::DUMMY_NODE_ID, value })
503603
} else if self.check_type() {
504604
// Parse type argument.
505-
GenericArg::Type(self.parse_ty()?)
605+
match self.parse_ty() {
606+
Ok(ty) => GenericArg::Type(ty),
607+
Err(err) => {
608+
// Try to recover from possible `const` arg without braces.
609+
return self.recover_const_arg(start, err).map(Some);
610+
}
611+
}
506612
} else {
507613
return Ok(None);
508614
};

src/test/ui/const-generics/const-expression-missing-braces.rs

+16-7
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,28 @@
33

44
fn foo<const C: usize>() {}
55

6+
const BAR: usize = 42;
7+
68
fn a() {
7-
let bar = 3;
8-
foo::<bar + 3>();
9-
//~^ ERROR expected one of `!`, `(`, `,`, `>`, `?`, `for`, lifetime, or path, found `3`
9+
foo::<BAR + 3>();
10+
//~^ ERROR expected one of
1011
}
1112
fn b() {
12-
let bar = 3;
13-
foo::<bar + bar>();
13+
foo::<BAR + BAR>();
1414
//~^ ERROR likely `const` expression parsed as trait bounds
1515
}
1616
fn c() {
17-
let bar = 3;
1817
foo::<3 + 3>(); // ok
1918
}
20-
19+
fn d() {
20+
foo::<BAR - 3>();
21+
//~^ ERROR expected one of
22+
}
23+
fn e() {
24+
foo::<BAR - BAR>();
25+
//~^ ERROR expected one of
26+
}
27+
fn f() {
28+
foo::<100 - BAR>(); // ok
29+
}
2130
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,46 @@
1-
error: expected one of `!`, `(`, `,`, `>`, `?`, `for`, lifetime, or path, found `3`
2-
--> $DIR/const-expression-missing-braces.rs:8:17
1+
error: expected one of `,` or `>`, found `3`
2+
--> $DIR/const-expression-missing-braces.rs:9:17
33
|
4-
LL | foo::<bar + 3>();
5-
| ^ expected one of 8 possible tokens
4+
LL | foo::<BAR + 3>();
5+
| ^ expected one of `,` or `>`
6+
|
7+
help: to write a `const` expression, surround it with braces for it to be unambiguous
8+
|
9+
LL | foo::<{ BAR + 3 }>();
10+
| ^ ^
11+
12+
error: expected one of `,` or `>`, found `-`
13+
--> $DIR/const-expression-missing-braces.rs:20:15
14+
|
15+
LL | foo::<BAR - 3>();
16+
| ^ expected one of `,` or `>`
17+
|
18+
help: to write a `const` expression, surround it with braces for it to be unambiguous
19+
|
20+
LL | foo::<{ BAR - 3 }>();
21+
| ^ ^
22+
23+
error: expected one of `,` or `>`, found `-`
24+
--> $DIR/const-expression-missing-braces.rs:24:15
25+
|
26+
LL | foo::<BAR - BAR>();
27+
| ^ expected one of `,` or `>`
28+
|
29+
help: to write a `const` expression, surround it with braces for it to be unambiguous
30+
|
31+
LL | foo::<{ BAR - BAR }>();
32+
| ^ ^
633

734
error: likely `const` expression parsed as trait bounds
835
--> $DIR/const-expression-missing-braces.rs:13:11
936
|
10-
LL | foo::<bar + bar>();
37+
LL | foo::<BAR + BAR>();
1138
| ^^^^^^^^^ parsed as trait bounds but traits weren't found
1239
|
1340
help: if you meant to write a `const` expression, surround the expression with braces
1441
|
15-
LL | foo::<{ bar + bar }>();
42+
LL | foo::<{ BAR + BAR }>();
1643
| ^ ^
1744

18-
error: aborting due to 2 previous errors
45+
error: aborting due to 4 previous errors
1946

Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
type closure = Box<lt/fn()>;
2-
//~^ ERROR expected one of `!`, `(`, `+`, `,`, `::`, `<`, or `>`, found `/`
2+
//~^ ERROR expected one of

src/test/ui/rfc-2497-if-let-chains/disallowed-positions.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ fn inside_const_generic_arguments() {
232232
// In the cases above we have `ExprKind::Block` to help us out.
233233
// Below however, we would not have a block and so an implementation might go
234234
// from visiting expressions to types without banning `let` expressions down the tree.
235-
// The parser admits non-IDENT expressions in const generic arguments and is caught by the
235+
// The parser admits non-IDENT expressions in const generic arguments and is caught by the
236236
// test below.
237237
if A::<
238238
true && let 1 = 1 //~ ERROR `let` expressions are not supported here

0 commit comments

Comments
 (0)