Skip to content

Commit 27801f7

Browse files
committed
Suggest enclosing const expression in block
When encountering `foo::<1 + 1>()`, suggest `foo::<{ 1 + 1 }>()`. Fix rust-lang#61175.
1 parent 4ff32c0 commit 27801f7

File tree

8 files changed

+177
-17
lines changed

8 files changed

+177
-17
lines changed

src/libsyntax/ast.rs

+17
Original file line numberDiff line numberDiff line change
@@ -1254,6 +1254,23 @@ pub enum ExprKind {
12541254
Err,
12551255
}
12561256

1257+
impl ExprKind {
1258+
/// Whether this expression can appear in a contst argument without being surrounded by braces.
1259+
///
1260+
/// Only used in error recovery.
1261+
pub(crate) fn is_valid_const_on_its_own(&self) -> bool {
1262+
match self {
1263+
ExprKind::Tup(_) |
1264+
ExprKind::Lit(_) |
1265+
ExprKind::Type(..) |
1266+
ExprKind::Path(..) |
1267+
ExprKind::Unary(..) |
1268+
ExprKind::Err => true,
1269+
_ => false,
1270+
}
1271+
}
1272+
}
1273+
12571274
/// The explicit `Self` type in a "qualified path". The actual
12581275
/// path, including the trait and the associated item, is stored
12591276
/// separately. `position` represents the index of the associated

src/libsyntax/parse/parser.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@ use std::path::PathBuf;
3838

3939
bitflags::bitflags! {
4040
struct Restrictions: u8 {
41-
const STMT_EXPR = 1 << 0;
42-
const NO_STRUCT_LITERAL = 1 << 1;
41+
const STMT_EXPR = 1 << 0;
42+
const NO_STRUCT_LITERAL = 1 << 1;
43+
const CONST_EXPR_RECOVERY = 1 << 2;
4344
}
4445
}
4546

src/libsyntax/parse/parser/expr.rs

+15-2
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,12 @@ impl<'a> Parser<'a> {
186186
self.last_type_ascription = None;
187187
return Ok(lhs);
188188
}
189+
(true, Some(AssocOp::Greater)) if self.restrictions.contains(
190+
Restrictions::CONST_EXPR_RECOVERY,
191+
) => { // Recovering likely const argument without braces.
192+
self.last_type_ascription = None;
193+
return Ok(lhs);
194+
}
189195
(true, Some(_)) => {
190196
// We've found an expression that would be parsed as a statement, but the next
191197
// token implies this should be parsed as an expression.
@@ -205,6 +211,11 @@ impl<'a> Parser<'a> {
205211
}
206212
self.expected_tokens.push(TokenType::Operator);
207213
while let Some(op) = AssocOp::from_token(&self.token) {
214+
if let (AssocOp::Greater, true) = (&op, self.restrictions.contains(
215+
Restrictions::CONST_EXPR_RECOVERY,
216+
)) { // Recovering likely const argument without braces.
217+
return Ok(lhs);
218+
}
208219

209220
// Adjust the span for interpolated LHS to point to the `$lhs` token and not to what
210221
// it refers to. Interpolated identifiers are unwrapped early and never show up here
@@ -341,8 +352,10 @@ impl<'a> Parser<'a> {
341352

342353
/// Checks if this expression is a successfully parsed statement.
343354
fn expr_is_complete(&self, e: &Expr) -> bool {
344-
self.restrictions.contains(Restrictions::STMT_EXPR) &&
345-
!classify::expr_requires_semi_to_be_stmt(e)
355+
(self.restrictions.contains(Restrictions::STMT_EXPR) &&
356+
!classify::expr_requires_semi_to_be_stmt(e)) ||
357+
(self.restrictions.contains(Restrictions::CONST_EXPR_RECOVERY) &&
358+
(self.token == token::Lt || self.token == token::Gt || self.token == token::Comma))
346359
}
347360

348361
fn is_at_start_of_range_notation_rhs(&self) -> bool {

src/libsyntax/parse/parser/path.rs

+43-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::{Parser, PResult, TokenType};
1+
use super::{Parser, PResult, Restrictions, TokenType};
22

33
use crate::{maybe_whole, ThinVec};
44
use crate::ast::{self, QSelf, Path, PathSegment, Ident, ParenthesizedArgs, AngleBracketedArgs};
@@ -414,10 +414,52 @@ impl<'a> Parser<'a> {
414414
assoc_ty_constraints.push(span);
415415
} else if self.check_const_arg() {
416416
// Parse const argument.
417+
let invalid = self.look_ahead(1, |t| t != &token::Lt && t != &token::Comma);
417418
let expr = if let token::OpenDelim(token::Brace) = self.token.kind {
418419
self.parse_block_expr(
419420
None, self.token.span, BlockCheckMode::Default, ThinVec::new()
420421
)?
422+
} else if invalid {
423+
// This can't possibly be a valid const arg, it is likely missing braces.
424+
let snapshot = self.clone();
425+
match self.parse_expr_res(Restrictions::CONST_EXPR_RECOVERY, None) {
426+
Ok(expr) => {
427+
if self.token == token::Comma || self.token == token::Gt {
428+
// We parsed the whole const argument successfully without braces.
429+
if !expr.node.is_valid_const_on_its_own() {
430+
// But it wasn't a literal, so we emit a custom error and
431+
// suggest the appropriate code.
432+
let msg =
433+
"complex const arguments must be surrounded by braces";
434+
let appl = Applicability::MachineApplicable;
435+
self.span_fatal(expr.span, msg)
436+
.multipart_suggestion(
437+
"surround this const argument in braces",
438+
vec![
439+
(expr.span.shrink_to_lo(), "{ ".to_string()),
440+
(expr.span.shrink_to_hi(), " }".to_string()),
441+
],
442+
appl,
443+
)
444+
.emit();
445+
}
446+
expr
447+
} else {
448+
// We parsed *some* expression, but it isn't the whole argument
449+
// so we can't ensure it was a const argument with missing braces.
450+
// Roll-back and emit a regular parser error.
451+
mem::replace(self, snapshot);
452+
self.parse_literal_maybe_minus()?
453+
}
454+
}
455+
Err(mut err) => {
456+
// We couldn't parse an expression successfully.
457+
// Roll-back, hide the error and emit a regular parser error.
458+
err.cancel();
459+
mem::replace(self, snapshot);
460+
self.parse_literal_maybe_minus()?
461+
}
462+
}
421463
} else if self.token.is_ident() {
422464
// FIXME(const_generics): to distinguish between idents for types and consts,
423465
// we should introduce a GenericArg::Ident in the AST and distinguish when

src/test/ui/const-generics/const-expression-parameter.rs

+18-1
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,30 @@ fn foo_a() {
1010
}
1111

1212
fn foo_b() {
13-
i32_identity::<1 + 2>(); //~ ERROR expected one of `,` or `>`, found `+`
13+
i32_identity::<1 + 2>(); //~ ERROR complex const arguments must be surrounded by braces
1414
}
1515

1616
fn foo_c() {
1717
i32_identity::< -1 >(); // ok
1818
}
1919

20+
fn foo_d() {
21+
i32_identity::<1 + 2, 3 + 4>();
22+
//~^ ERROR complex const arguments must be surrounded by braces
23+
//~| ERROR complex const arguments must be surrounded by braces
24+
//~| ERROR wrong number of const arguments: expected 1, found 2
25+
}
26+
27+
fn baz<const X: i32, const Y: i32>() -> i32 {
28+
42
29+
}
30+
31+
fn foo_e() {
32+
baz::<1 + 2, 3 + 4>();
33+
//~^ ERROR complex const arguments must be surrounded by braces
34+
//~| ERROR complex const arguments must be surrounded by braces
35+
}
36+
2037
fn main() {
2138
i32_identity::<5>(); // ok
2239
}
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,52 @@
1-
error: expected one of `,` or `>`, found `+`
2-
--> $DIR/const-expression-parameter.rs:13:22
1+
error: complex const arguments must be surrounded by braces
2+
--> $DIR/const-expression-parameter.rs:13:20
33
|
44
LL | i32_identity::<1 + 2>();
5-
| ^ expected one of `,` or `>` here
5+
| ^^^^^
6+
help: surround this const argument in braces
7+
|
8+
LL | i32_identity::<{ 1 + 2 }>();
9+
| ^ ^
10+
11+
error: complex const arguments must be surrounded by braces
12+
--> $DIR/const-expression-parameter.rs:21:20
13+
|
14+
LL | i32_identity::<1 + 2, 3 + 4>();
15+
| ^^^^^
16+
help: surround this const argument in braces
17+
|
18+
LL | i32_identity::<{ 1 + 2 }, 3 + 4>();
19+
| ^ ^
20+
21+
error: complex const arguments must be surrounded by braces
22+
--> $DIR/const-expression-parameter.rs:21:27
23+
|
24+
LL | i32_identity::<1 + 2, 3 + 4>();
25+
| ^^^^^
26+
help: surround this const argument in braces
27+
|
28+
LL | i32_identity::<1 + 2, { 3 + 4 }>();
29+
| ^ ^
30+
31+
error: complex const arguments must be surrounded by braces
32+
--> $DIR/const-expression-parameter.rs:32:11
33+
|
34+
LL | baz::<1 + 2, 3 + 4>();
35+
| ^^^^^
36+
help: surround this const argument in braces
37+
|
38+
LL | baz::<{ 1 + 2 }, 3 + 4>();
39+
| ^ ^
40+
41+
error: complex const arguments must be surrounded by braces
42+
--> $DIR/const-expression-parameter.rs:32:18
43+
|
44+
LL | baz::<1 + 2, 3 + 4>();
45+
| ^^^^^
46+
help: surround this const argument in braces
47+
|
48+
LL | baz::<1 + 2, { 3 + 4 }>();
49+
| ^ ^
650

751
warning: the feature `const_generics` is incomplete and may cause the compiler to crash
852
--> $DIR/const-expression-parameter.rs:1:12
@@ -12,5 +56,12 @@ LL | #![feature(const_generics)]
1256
|
1357
= note: `#[warn(incomplete_features)]` on by default
1458

15-
error: aborting due to previous error
59+
error[E0107]: wrong number of const arguments: expected 1, found 2
60+
--> $DIR/const-expression-parameter.rs:21:27
61+
|
62+
LL | i32_identity::<1 + 2, 3 + 4>();
63+
| ^^^^^ unexpected const argument
64+
65+
error: aborting due to 6 previous errors
1666

67+
For more information about this error, try `rustc --explain E0107`.

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ fn inside_const_generic_arguments() {
239239
// admit non-IDENT expressions in const generic arguments.
240240

241241
if A::<
242-
true && let 1 = 1 //~ ERROR expected one of `,` or `>`, found `&&`
243-
>::O == 5 {}
242+
true && let 1 = 1 //~ ERROR complex const arguments must be surrounded by braces
243+
>::O == 5 {} //~ ERROR chained comparison operators require parentheses
244+
//~^ ERROR expected one of `,`, `.`, `>`, `?`, or an operator, found `{`
244245
}

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

+23-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,26 @@
1-
error: expected one of `,` or `>`, found `&&`
2-
--> $DIR/disallowed-positions.rs:242:14
1+
error: chained comparison operators require parentheses
2+
--> $DIR/disallowed-positions.rs:243:5
33
|
4-
LL | true && let 1 = 1
5-
| ^^ expected one of `,` or `>` here
4+
LL | >::O == 5 {}
5+
| ^^^^^^^^^
6+
7+
error: complex const arguments must be surrounded by braces
8+
--> $DIR/disallowed-positions.rs:242:9
9+
|
10+
LL | / true && let 1 = 1
11+
LL | | >::O == 5 {}
12+
| |_____________^
13+
help: surround this const argument in braces
14+
|
15+
LL | { true && let 1 = 1
16+
LL | >::O == 5 } {}
17+
|
18+
19+
error: expected one of `,`, `.`, `>`, `?`, or an operator, found `{`
20+
--> $DIR/disallowed-positions.rs:243:15
21+
|
22+
LL | >::O == 5 {}
23+
| ^ expected one of `,`, `.`, `>`, `?`, or an operator here
624

725
error: `let` expressions are not supported here
826
--> $DIR/disallowed-positions.rs:32:9
@@ -983,7 +1001,7 @@ error[E0019]: constant contains unimplemented expression type
9831001
LL | true && let 1 = 1
9841002
| ^
9851003

986-
error: aborting due to 109 previous errors
1004+
error: aborting due to 111 previous errors
9871005

9881006
Some errors have detailed explanations: E0019, E0277, E0308, E0600, E0614.
9891007
For more information about an error, try `rustc --explain E0019`.

0 commit comments

Comments
 (0)