Skip to content

Commit

Permalink
Rollup merge of rust-lang#107190 - fmease:fix-81698, r=compiler-errors
Browse files Browse the repository at this point in the history
Recover from more const arguments that are not wrapped in curly braces

Recover from some array, borrow, tuple & arithmetic expressions in const argument positions that lack curly braces and provide a suggestion to fix the issue continuing where rust-lang#92884 left off. Examples of such expressions: `[]`, `[0]`, `[1, 2]`, `[0; 0xff]`, `&9`, `("", 0)` and `(1 + 2) * 3` (we previously did not recover from them).

I am not entirely happy with my current solution because the code that recovers from `[0]` (coinciding with a malformed slice type) and `[0; 0]` (coinciding with a malformed array type) is quite fragile as the aforementioned snippets are actually successfully parsed as types by `parse_ty` since it itself already recovers from them (returning `[⟨error⟩]` and `[⟨error⟩; 0]` respectively) meaning I have to manually look for `TyKind::Err`s and construct a separate diagnostic for the suggestion to attach to (thereby emitting two diagnostics in total).

Fixes rust-lang#81698.
`@rustbot` label A-diagnostics
r? diagnostics
  • Loading branch information
matthiaskrgr authored Jan 28, 2023
2 parents 3b6593a + 80a1536 commit 260e048
Show file tree
Hide file tree
Showing 4 changed files with 188 additions and 22 deletions.
22 changes: 22 additions & 0 deletions compiler/rustc_parse/src/parser/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2353,6 +2353,28 @@ impl<'a> Parser<'a> {
Err(err)
}

/// Try to recover from an unbraced const argument whose first token [could begin a type][ty].
///
/// [ty]: token::Token::can_begin_type
pub(crate) fn recover_unbraced_const_arg_that_can_begin_ty(
&mut self,
mut snapshot: SnapshotParser<'a>,
) -> Option<P<ast::Expr>> {
match snapshot.parse_expr_res(Restrictions::CONST_EXPR, None) {
// Since we don't know the exact reason why we failed to parse the type or the
// expression, employ a simple heuristic to weed out some pathological cases.
Ok(expr) if let token::Comma | token::Gt = snapshot.token.kind => {
self.restore_snapshot(snapshot);
Some(expr)
}
Ok(_) => None,
Err(err) => {
err.cancel();
None
}
}
}

/// Creates a dummy const argument, and reports that the expression must be enclosed in braces
pub fn dummy_const_arg_needs_braces(
&self,
Expand Down
48 changes: 34 additions & 14 deletions compiler/rustc_parse/src/parser/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -675,22 +675,42 @@ impl<'a> Parser<'a> {
GenericArg::Const(self.parse_const_arg()?)
} else if self.check_type() {
// Parse type argument.
let is_const_fn =
self.look_ahead(1, |t| t.kind == token::OpenDelim(Delimiter::Parenthesis));
let mut snapshot = self.create_snapshot_for_diagnostic();

// Proactively create a parser snapshot enabling us to rewind and try to reparse the
// input as a const expression in case we fail to parse a type. If we successfully
// do so, we will report an error that it needs to be wrapped in braces.
let mut snapshot = None;
if self.may_recover() && self.token.can_begin_expr() {
snapshot = Some(self.create_snapshot_for_diagnostic());
}

match self.parse_ty() {
Ok(ty) => GenericArg::Type(ty),
Ok(ty) => {
// Since the type parser recovers from some malformed slice and array types and
// successfully returns a type, we need to look for `TyKind::Err`s in the
// type to determine if error recovery has occurred and if the input is not a
// syntactically valid type after all.
if let ast::TyKind::Slice(inner_ty) | ast::TyKind::Array(inner_ty, _) = &ty.kind
&& let ast::TyKind::Err = inner_ty.kind
&& let Some(snapshot) = snapshot
&& let Some(expr) = self.recover_unbraced_const_arg_that_can_begin_ty(snapshot)
{
return Ok(Some(self.dummy_const_arg_needs_braces(
self.struct_span_err(expr.span, "invalid const generic expression"),
expr.span,
)));
}

GenericArg::Type(ty)
}
Err(err) => {
if is_const_fn {
match (*snapshot).parse_expr_res(Restrictions::CONST_EXPR, None) {
Ok(expr) => {
self.restore_snapshot(snapshot);
return Ok(Some(self.dummy_const_arg_needs_braces(err, expr.span)));
}
Err(err) => {
err.cancel();
}
}
if let Some(snapshot) = snapshot
&& let Some(expr) = self.recover_unbraced_const_arg_that_can_begin_ty(snapshot)
{
return Ok(Some(self.dummy_const_arg_needs_braces(
err,
expr.span,
)));
}
// Try to recover from possible `const` arg without braces.
return self.recover_const_arg(start, err).map(Some);
Expand Down
34 changes: 29 additions & 5 deletions tests/ui/const-generics/bad-const-generic-exprs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,34 @@ fn main() {
let _: Wow<A.0>;
//~^ ERROR expected one of
//~| HELP expressions must be enclosed in braces to be used as const generic arguments

// FIXME(compiler-errors): This one is still unsatisfying,
// and probably a case I could see someone typing by accident..
let _: Wow<[]>;
//~^ ERROR expected type
//~| HELP expressions must be enclosed in braces to be used as const generic arguments
let _: Wow<[12]>;
//~^ ERROR expected type, found
//~| ERROR type provided when a constant was expected
//~^ ERROR expected type
//~| ERROR invalid const generic expression
//~| HELP expressions must be enclosed in braces to be used as const generic arguments
let _: Wow<[0, 1, 3]>;
//~^ ERROR expected type
//~| HELP expressions must be enclosed in braces to be used as const generic arguments
let _: Wow<[0xff; 8]>;
//~^ ERROR expected type
//~| ERROR invalid const generic expression
//~| HELP expressions must be enclosed in braces to be used as const generic arguments
let _: Wow<[1, 2]>; // Regression test for issue #81698.
//~^ ERROR expected type
//~| HELP expressions must be enclosed in braces to be used as const generic arguments
let _: Wow<&0>;
//~^ ERROR expected type
//~| HELP expressions must be enclosed in braces to be used as const generic arguments
let _: Wow<("", 0)>;
//~^ ERROR expected type
//~| HELP expressions must be enclosed in braces to be used as const generic arguments
let _: Wow<(1 + 2) * 3>;
//~^ ERROR expected type
//~| HELP expressions must be enclosed in braces to be used as const generic arguments
// FIXME(fmease): This one is pretty bad.
let _: Wow<!0>;
//~^ ERROR expected one of
//~| HELP you might have meant to end the type parameters here
}
106 changes: 103 additions & 3 deletions tests/ui/const-generics/bad-const-generic-exprs.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,118 @@ help: expressions must be enclosed in braces to be used as const generic argumen
LL | let _: Wow<{ A.0 }>;
| + +

error: expected type, found `]`
--> $DIR/bad-const-generic-exprs.rs:16:17
|
LL | let _: Wow<[]>;
| ^ expected type
|
help: expressions must be enclosed in braces to be used as const generic arguments
|
LL | let _: Wow<{ [] }>;
| + +

error: expected type, found `12`
--> $DIR/bad-const-generic-exprs.rs:19:17
|
LL | let _: Wow<[12]>;
| ^^ expected type

error[E0747]: type provided when a constant was expected
error: invalid const generic expression
--> $DIR/bad-const-generic-exprs.rs:19:16
|
LL | let _: Wow<[12]>;
| ^^^^
|
help: expressions must be enclosed in braces to be used as const generic arguments
|
LL | let _: Wow<{ [12] }>;
| + +

error: expected type, found `0`
--> $DIR/bad-const-generic-exprs.rs:23:17
|
LL | let _: Wow<[0, 1, 3]>;
| ^ expected type
|
help: expressions must be enclosed in braces to be used as const generic arguments
|
LL | let _: Wow<{ [0, 1, 3] }>;
| + +

error: expected type, found `0xff`
--> $DIR/bad-const-generic-exprs.rs:26:17
|
LL | let _: Wow<[0xff; 8]>;
| ^^^^ expected type

error: invalid const generic expression
--> $DIR/bad-const-generic-exprs.rs:26:16
|
LL | let _: Wow<[0xff; 8]>;
| ^^^^^^^^^
|
help: expressions must be enclosed in braces to be used as const generic arguments
|
LL | let _: Wow<{ [0xff; 8] }>;
| + +

error: expected type, found `1`
--> $DIR/bad-const-generic-exprs.rs:30:17
|
LL | let _: Wow<[1, 2]>; // Regression test for issue #81698.
| ^ expected type
|
help: expressions must be enclosed in braces to be used as const generic arguments
|
LL | let _: Wow<{ [1, 2] }>; // Regression test for issue #81698.
| + +

error: expected type, found `0`
--> $DIR/bad-const-generic-exprs.rs:33:17
|
LL | let _: Wow<&0>;
| ^ expected type
|
help: expressions must be enclosed in braces to be used as const generic arguments
|
LL | let _: Wow<{ &0 }>;
| + +

error: expected type, found `""`
--> $DIR/bad-const-generic-exprs.rs:36:17
|
LL | let _: Wow<("", 0)>;
| ^^ expected type
|
help: expressions must be enclosed in braces to be used as const generic arguments
|
LL | let _: Wow<{ ("", 0) }>;
| + +

error: expected type, found `1`
--> $DIR/bad-const-generic-exprs.rs:39:17
|
LL | let _: Wow<(1 + 2) * 3>;
| ^ expected type
|
help: expressions must be enclosed in braces to be used as const generic arguments
|
LL | let _: Wow<{ (1 + 2) * 3 }>;
| + +

error: expected one of `,` or `>`, found `0`
--> $DIR/bad-const-generic-exprs.rs:43:17
|
LL | let _: Wow<!0>;
| - ^ expected one of `,` or `>`
| |
| while parsing the type for `_`
|
help: you might have meant to end the type parameters here
|
LL | let _: Wow<!>0>;
| +

error: aborting due to 6 previous errors
error: aborting due to 15 previous errors

For more information about this error, try `rustc --explain E0747`.

0 comments on commit 260e048

Please sign in to comment.