Skip to content

Commit

Permalink
Suggest enclosing const expression in block
Browse files Browse the repository at this point in the history
When encountering `foo::<1 + 1>()`, suggest `foo::<{ 1 + 1 }>()`.

Fix #61175.
  • Loading branch information
estebank committed Sep 23, 2019
1 parent 4ff32c0 commit 27801f7
Show file tree
Hide file tree
Showing 8 changed files with 177 additions and 17 deletions.
17 changes: 17 additions & 0 deletions src/libsyntax/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1254,6 +1254,23 @@ pub enum ExprKind {
Err,
}

impl ExprKind {
/// Whether this expression can appear in a contst argument without being surrounded by braces.
///
/// Only used in error recovery.
pub(crate) fn is_valid_const_on_its_own(&self) -> bool {
match self {
ExprKind::Tup(_) |
ExprKind::Lit(_) |
ExprKind::Type(..) |
ExprKind::Path(..) |
ExprKind::Unary(..) |
ExprKind::Err => true,
_ => false,
}
}
}

/// The explicit `Self` type in a "qualified path". The actual
/// path, including the trait and the associated item, is stored
/// separately. `position` represents the index of the associated
Expand Down
5 changes: 3 additions & 2 deletions src/libsyntax/parse/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ use std::path::PathBuf;

bitflags::bitflags! {
struct Restrictions: u8 {
const STMT_EXPR = 1 << 0;
const NO_STRUCT_LITERAL = 1 << 1;
const STMT_EXPR = 1 << 0;
const NO_STRUCT_LITERAL = 1 << 1;
const CONST_EXPR_RECOVERY = 1 << 2;
}
}

Expand Down
17 changes: 15 additions & 2 deletions src/libsyntax/parse/parser/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,12 @@ impl<'a> Parser<'a> {
self.last_type_ascription = None;
return Ok(lhs);
}
(true, Some(AssocOp::Greater)) if self.restrictions.contains(
Restrictions::CONST_EXPR_RECOVERY,
) => { // Recovering likely const argument without braces.
self.last_type_ascription = None;
return Ok(lhs);
}
(true, Some(_)) => {
// We've found an expression that would be parsed as a statement, but the next
// token implies this should be parsed as an expression.
Expand All @@ -205,6 +211,11 @@ impl<'a> Parser<'a> {
}
self.expected_tokens.push(TokenType::Operator);
while let Some(op) = AssocOp::from_token(&self.token) {
if let (AssocOp::Greater, true) = (&op, self.restrictions.contains(
Restrictions::CONST_EXPR_RECOVERY,
)) { // Recovering likely const argument without braces.
return Ok(lhs);
}

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

/// Checks if this expression is a successfully parsed statement.
fn expr_is_complete(&self, e: &Expr) -> bool {
self.restrictions.contains(Restrictions::STMT_EXPR) &&
!classify::expr_requires_semi_to_be_stmt(e)
(self.restrictions.contains(Restrictions::STMT_EXPR) &&
!classify::expr_requires_semi_to_be_stmt(e)) ||
(self.restrictions.contains(Restrictions::CONST_EXPR_RECOVERY) &&
(self.token == token::Lt || self.token == token::Gt || self.token == token::Comma))
}

fn is_at_start_of_range_notation_rhs(&self) -> bool {
Expand Down
44 changes: 43 additions & 1 deletion src/libsyntax/parse/parser/path.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::{Parser, PResult, TokenType};
use super::{Parser, PResult, Restrictions, TokenType};

use crate::{maybe_whole, ThinVec};
use crate::ast::{self, QSelf, Path, PathSegment, Ident, ParenthesizedArgs, AngleBracketedArgs};
Expand Down Expand Up @@ -414,10 +414,52 @@ impl<'a> Parser<'a> {
assoc_ty_constraints.push(span);
} else if self.check_const_arg() {
// Parse const argument.
let invalid = self.look_ahead(1, |t| t != &token::Lt && t != &token::Comma);
let expr = if let token::OpenDelim(token::Brace) = self.token.kind {
self.parse_block_expr(
None, self.token.span, BlockCheckMode::Default, ThinVec::new()
)?
} else if invalid {
// This can't possibly be a valid const arg, it is likely missing braces.
let snapshot = self.clone();
match self.parse_expr_res(Restrictions::CONST_EXPR_RECOVERY, None) {
Ok(expr) => {
if self.token == token::Comma || self.token == token::Gt {
// We parsed the whole const argument successfully without braces.
if !expr.node.is_valid_const_on_its_own() {
// But it wasn't a literal, so we emit a custom error and
// suggest the appropriate code.
let msg =
"complex const arguments must be surrounded by braces";
let appl = Applicability::MachineApplicable;
self.span_fatal(expr.span, msg)
.multipart_suggestion(
"surround this const argument in braces",
vec![
(expr.span.shrink_to_lo(), "{ ".to_string()),
(expr.span.shrink_to_hi(), " }".to_string()),
],
appl,
)
.emit();
}
expr
} else {
// We parsed *some* expression, but it isn't the whole argument
// so we can't ensure it was a const argument with missing braces.
// Roll-back and emit a regular parser error.
mem::replace(self, snapshot);
self.parse_literal_maybe_minus()?
}
}
Err(mut err) => {
// We couldn't parse an expression successfully.
// Roll-back, hide the error and emit a regular parser error.
err.cancel();
mem::replace(self, snapshot);
self.parse_literal_maybe_minus()?
}
}
} else if self.token.is_ident() {
// FIXME(const_generics): to distinguish between idents for types and consts,
// we should introduce a GenericArg::Ident in the AST and distinguish when
Expand Down
19 changes: 18 additions & 1 deletion src/test/ui/const-generics/const-expression-parameter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,30 @@ fn foo_a() {
}

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

fn foo_c() {
i32_identity::< -1 >(); // ok
}

fn foo_d() {
i32_identity::<1 + 2, 3 + 4>();
//~^ ERROR complex const arguments must be surrounded by braces
//~| ERROR complex const arguments must be surrounded by braces
//~| ERROR wrong number of const arguments: expected 1, found 2
}

fn baz<const X: i32, const Y: i32>() -> i32 {
42
}

fn foo_e() {
baz::<1 + 2, 3 + 4>();
//~^ ERROR complex const arguments must be surrounded by braces
//~| ERROR complex const arguments must be surrounded by braces
}

fn main() {
i32_identity::<5>(); // ok
}
59 changes: 55 additions & 4 deletions src/test/ui/const-generics/const-expression-parameter.stderr
Original file line number Diff line number Diff line change
@@ -1,8 +1,52 @@
error: expected one of `,` or `>`, found `+`
--> $DIR/const-expression-parameter.rs:13:22
error: complex const arguments must be surrounded by braces
--> $DIR/const-expression-parameter.rs:13:20
|
LL | i32_identity::<1 + 2>();
| ^ expected one of `,` or `>` here
| ^^^^^
help: surround this const argument in braces
|
LL | i32_identity::<{ 1 + 2 }>();
| ^ ^

error: complex const arguments must be surrounded by braces
--> $DIR/const-expression-parameter.rs:21:20
|
LL | i32_identity::<1 + 2, 3 + 4>();
| ^^^^^
help: surround this const argument in braces
|
LL | i32_identity::<{ 1 + 2 }, 3 + 4>();
| ^ ^

error: complex const arguments must be surrounded by braces
--> $DIR/const-expression-parameter.rs:21:27
|
LL | i32_identity::<1 + 2, 3 + 4>();
| ^^^^^
help: surround this const argument in braces
|
LL | i32_identity::<1 + 2, { 3 + 4 }>();
| ^ ^

error: complex const arguments must be surrounded by braces
--> $DIR/const-expression-parameter.rs:32:11
|
LL | baz::<1 + 2, 3 + 4>();
| ^^^^^
help: surround this const argument in braces
|
LL | baz::<{ 1 + 2 }, 3 + 4>();
| ^ ^

error: complex const arguments must be surrounded by braces
--> $DIR/const-expression-parameter.rs:32:18
|
LL | baz::<1 + 2, 3 + 4>();
| ^^^^^
help: surround this const argument in braces
|
LL | baz::<1 + 2, { 3 + 4 }>();
| ^ ^

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

error: aborting due to previous error
error[E0107]: wrong number of const arguments: expected 1, found 2
--> $DIR/const-expression-parameter.rs:21:27
|
LL | i32_identity::<1 + 2, 3 + 4>();
| ^^^^^ unexpected const argument

error: aborting due to 6 previous errors

For more information about this error, try `rustc --explain E0107`.
5 changes: 3 additions & 2 deletions src/test/ui/rfc-2497-if-let-chains/disallowed-positions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ fn inside_const_generic_arguments() {
// admit non-IDENT expressions in const generic arguments.

if A::<
true && let 1 = 1 //~ ERROR expected one of `,` or `>`, found `&&`
>::O == 5 {}
true && let 1 = 1 //~ ERROR complex const arguments must be surrounded by braces
>::O == 5 {} //~ ERROR chained comparison operators require parentheses
//~^ ERROR expected one of `,`, `.`, `>`, `?`, or an operator, found `{`
}
28 changes: 23 additions & 5 deletions src/test/ui/rfc-2497-if-let-chains/disallowed-positions.stderr
Original file line number Diff line number Diff line change
@@ -1,8 +1,26 @@
error: expected one of `,` or `>`, found `&&`
--> $DIR/disallowed-positions.rs:242:14
error: chained comparison operators require parentheses
--> $DIR/disallowed-positions.rs:243:5
|
LL | true && let 1 = 1
| ^^ expected one of `,` or `>` here
LL | >::O == 5 {}
| ^^^^^^^^^

error: complex const arguments must be surrounded by braces
--> $DIR/disallowed-positions.rs:242:9
|
LL | / true && let 1 = 1
LL | | >::O == 5 {}
| |_____________^
help: surround this const argument in braces
|
LL | { true && let 1 = 1
LL | >::O == 5 } {}
|

error: expected one of `,`, `.`, `>`, `?`, or an operator, found `{`
--> $DIR/disallowed-positions.rs:243:15
|
LL | >::O == 5 {}
| ^ expected one of `,`, `.`, `>`, `?`, or an operator here

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

error: aborting due to 109 previous errors
error: aborting due to 111 previous errors

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

0 comments on commit 27801f7

Please sign in to comment.