Skip to content

Commit 20b1e05

Browse files
committed
Auto merge of rust-lang#77502 - varkor:const-generics-suggest-enclosing-braces, r=petrochenkov
Suggest that expressions that look like const generic arguments should be enclosed in brackets I pulled out the changes for const expressions from rust-lang#71592 (without the trait object diagnostic changes) and made some small changes; the implementation is `@estebank's.` We're also going to want to make some changes separately to account for trait objects (they result in poor diagnostics, as is evident from one of the test cases here), such as an adaption of rust-lang#72273. Fixes rust-lang#70753. r? `@petrochenkov`
2 parents 824f900 + ac14540 commit 20b1e05

19 files changed

+782
-35
lines changed

compiler/rustc_ast/src/ast.rs

+9
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,15 @@ pub enum AngleBracketedArg {
222222
Constraint(AssocTyConstraint),
223223
}
224224

225+
impl AngleBracketedArg {
226+
pub fn span(&self) -> Span {
227+
match self {
228+
AngleBracketedArg::Arg(arg) => arg.span(),
229+
AngleBracketedArg::Constraint(constraint) => constraint.span,
230+
}
231+
}
232+
}
233+
225234
impl Into<Option<P<GenericArgs>>> for AngleBracketedArgs {
226235
fn into(self) -> Option<P<GenericArgs>> {
227236
Some(P(GenericArgs::AngleBracketed(self)))

compiler/rustc_ast/src/token.rs

+7
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,13 @@ impl TokenKind {
303303
_ => None,
304304
}
305305
}
306+
307+
pub fn should_end_const_arg(&self) -> bool {
308+
match self {
309+
Gt | Ge | BinOp(Shr) | BinOpEq(Shr) => true,
310+
_ => false,
311+
}
312+
}
306313
}
307314

308315
impl Token {

compiler/rustc_middle/src/ty/relate.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,7 @@ pub fn super_relate_consts<R: TypeRelation<'tcx>>(
490490
let eagerly_eval = |x: &'tcx ty::Const<'tcx>| x.eval(tcx, relation.param_env()).val;
491491

492492
// FIXME(eddyb) doesn't look like everything below checks that `a.ty == b.ty`.
493-
// We could probably always assert it early, as `const` generic parameters
493+
// We could probably always assert it early, as const generic parameters
494494
// are not allowed to depend on other generic parameters, i.e. are concrete.
495495
// (although there could be normalization differences)
496496

compiler/rustc_parse/src/parser/diagnostics.rs

+143-4
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
use super::ty::AllowPlus;
2-
use super::{BlockMode, Parser, PathStyle, SemiColonMode, SeqSep, TokenExpectType, TokenType};
2+
use super::TokenType;
3+
use super::{BlockMode, Parser, PathStyle, Restrictions, SemiColonMode, SeqSep, TokenExpectType};
34

45
use rustc_ast::ptr::P;
56
use rustc_ast::token::{self, Lit, LitKind, TokenKind};
67
use rustc_ast::util::parser::AssocOp;
78
use rustc_ast::{
8-
self as ast, AngleBracketedArgs, AttrVec, BinOpKind, BindingMode, Block, BlockCheckMode, Expr,
9-
ExprKind, Item, ItemKind, Mutability, Param, Pat, PatKind, Path, PathSegment, QSelf, Ty,
10-
TyKind,
9+
self as ast, AngleBracketedArg, AngleBracketedArgs, AnonConst, AttrVec, BinOpKind, BindingMode,
10+
Block, BlockCheckMode, Expr, ExprKind, GenericArg, Item, ItemKind, Mutability, Param, Pat,
11+
PatKind, Path, PathSegment, QSelf, Ty, TyKind,
1112
};
1213
use rustc_ast_pretty::pprust;
1314
use rustc_data_structures::fx::FxHashSet;
@@ -1780,4 +1781,142 @@ impl<'a> Parser<'a> {
17801781
}
17811782
}
17821783
}
1784+
1785+
/// Handle encountering a symbol in a generic argument list that is not a `,` or `>`. In this
1786+
/// case, we emit an error and try to suggest enclosing a const argument in braces if it looks
1787+
/// like the user has forgotten them.
1788+
pub fn handle_ambiguous_unbraced_const_arg(
1789+
&mut self,
1790+
args: &mut Vec<AngleBracketedArg>,
1791+
) -> PResult<'a, bool> {
1792+
// If we haven't encountered a closing `>`, then the argument is malformed.
1793+
// It's likely that the user has written a const expression without enclosing it
1794+
// in braces, so we try to recover here.
1795+
let arg = args.pop().unwrap();
1796+
// FIXME: for some reason using `unexpected` or `expected_one_of_not_found` has
1797+
// adverse side-effects to subsequent errors and seems to advance the parser.
1798+
// We are causing this error here exclusively in case that a `const` expression
1799+
// could be recovered from the current parser state, even if followed by more
1800+
// arguments after a comma.
1801+
let mut err = self.struct_span_err(
1802+
self.token.span,
1803+
&format!("expected one of `,` or `>`, found {}", super::token_descr(&self.token)),
1804+
);
1805+
err.span_label(self.token.span, "expected one of `,` or `>`");
1806+
match self.recover_const_arg(arg.span(), err) {
1807+
Ok(arg) => {
1808+
args.push(AngleBracketedArg::Arg(arg));
1809+
if self.eat(&token::Comma) {
1810+
return Ok(true); // Continue
1811+
}
1812+
}
1813+
Err(mut err) => {
1814+
args.push(arg);
1815+
// We will emit a more generic error later.
1816+
err.delay_as_bug();
1817+
}
1818+
}
1819+
return Ok(false); // Don't continue.
1820+
}
1821+
1822+
/// Handle a generic const argument that had not been enclosed in braces, and suggest enclosing
1823+
/// it braces. In this situation, unlike in `handle_ambiguous_unbraced_const_arg`, this is
1824+
/// almost certainly a const argument, so we always offer a suggestion.
1825+
pub fn handle_unambiguous_unbraced_const_arg(&mut self) -> PResult<'a, P<Expr>> {
1826+
let start = self.token.span;
1827+
let expr = self.parse_expr_res(Restrictions::CONST_EXPR, None).map_err(|mut err| {
1828+
err.span_label(
1829+
start.shrink_to_lo(),
1830+
"while parsing a const generic argument starting here",
1831+
);
1832+
err
1833+
})?;
1834+
if !self.expr_is_valid_const_arg(&expr) {
1835+
self.struct_span_err(
1836+
expr.span,
1837+
"expressions must be enclosed in braces to be used as const generic \
1838+
arguments",
1839+
)
1840+
.multipart_suggestion(
1841+
"enclose the `const` expression in braces",
1842+
vec![
1843+
(expr.span.shrink_to_lo(), "{ ".to_string()),
1844+
(expr.span.shrink_to_hi(), " }".to_string()),
1845+
],
1846+
Applicability::MachineApplicable,
1847+
)
1848+
.emit();
1849+
}
1850+
Ok(expr)
1851+
}
1852+
1853+
/// Try to recover from possible generic const argument without `{` and `}`.
1854+
///
1855+
/// When encountering code like `foo::< bar + 3 >` or `foo::< bar - baz >` we suggest
1856+
/// `foo::<{ bar + 3 }>` and `foo::<{ bar - baz }>`, respectively. We only provide a suggestion
1857+
/// if we think that that the resulting expression would be well formed.
1858+
pub fn recover_const_arg(
1859+
&mut self,
1860+
start: Span,
1861+
mut err: DiagnosticBuilder<'a>,
1862+
) -> PResult<'a, GenericArg> {
1863+
let is_op = AssocOp::from_token(&self.token)
1864+
.and_then(|op| {
1865+
if let AssocOp::Greater
1866+
| AssocOp::Less
1867+
| AssocOp::ShiftRight
1868+
| AssocOp::GreaterEqual
1869+
// Don't recover from `foo::<bar = baz>`, because this could be an attempt to
1870+
// assign a value to a defaulted generic parameter.
1871+
| AssocOp::Assign
1872+
| AssocOp::AssignOp(_) = op
1873+
{
1874+
None
1875+
} else {
1876+
Some(op)
1877+
}
1878+
})
1879+
.is_some();
1880+
// This will be true when a trait object type `Foo +` or a path which was a `const fn` with
1881+
// type params has been parsed.
1882+
let was_op =
1883+
matches!(self.prev_token.kind, token::BinOp(token::Plus | token::Shr) | token::Gt);
1884+
if !is_op && !was_op {
1885+
// We perform these checks and early return to avoid taking a snapshot unnecessarily.
1886+
return Err(err);
1887+
}
1888+
let snapshot = self.clone();
1889+
if is_op {
1890+
self.bump();
1891+
}
1892+
match self.parse_expr_res(Restrictions::CONST_EXPR, None) {
1893+
Ok(expr) => {
1894+
if token::Comma == self.token.kind || self.token.kind.should_end_const_arg() {
1895+
// Avoid the following output by checking that we consumed a full const arg:
1896+
// help: expressions must be enclosed in braces to be used as const generic
1897+
// arguments
1898+
// |
1899+
// LL | let sr: Vec<{ (u32, _, _) = vec![] };
1900+
// | ^ ^
1901+
err.multipart_suggestion(
1902+
"expressions must be enclosed in braces to be used as const generic \
1903+
arguments",
1904+
vec![
1905+
(start.shrink_to_lo(), "{ ".to_string()),
1906+
(expr.span.shrink_to_hi(), " }".to_string()),
1907+
],
1908+
Applicability::MaybeIncorrect,
1909+
);
1910+
let value = self.mk_expr_err(start.to(expr.span));
1911+
err.emit();
1912+
return Ok(GenericArg::Const(AnonConst { id: ast::DUMMY_NODE_ID, value }));
1913+
}
1914+
}
1915+
Err(mut err) => {
1916+
err.cancel();
1917+
}
1918+
}
1919+
*self = snapshot;
1920+
Err(err)
1921+
}
17831922
}

compiler/rustc_parse/src/parser/expr.rs

+13-1
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,18 @@ impl<'a> Parser<'a> {
359359
/// Also performs recovery for `and` / `or` which are mistaken for `&&` and `||` respectively.
360360
fn check_assoc_op(&self) -> Option<Spanned<AssocOp>> {
361361
let (op, span) = match (AssocOp::from_token(&self.token), self.token.ident()) {
362+
// When parsing const expressions, stop parsing when encountering `>`.
363+
(
364+
Some(
365+
AssocOp::ShiftRight
366+
| AssocOp::Greater
367+
| AssocOp::GreaterEqual
368+
| AssocOp::AssignOp(token::BinOpToken::Shr),
369+
),
370+
_,
371+
) if self.restrictions.contains(Restrictions::CONST_EXPR) => {
372+
return None;
373+
}
362374
(Some(op), _) => (op, self.token.span),
363375
(None, Some((Ident { name: sym::and, span }, false))) => {
364376
self.error_bad_logical_op("and", "&&", "conjunction");
@@ -1715,7 +1727,7 @@ impl<'a> Parser<'a> {
17151727
let lo = self.prev_token.span;
17161728
let pat = self.parse_top_pat(GateOr::No)?;
17171729
self.expect(&token::Eq)?;
1718-
let expr = self.with_res(Restrictions::NO_STRUCT_LITERAL, |this| {
1730+
let expr = self.with_res(self.restrictions | Restrictions::NO_STRUCT_LITERAL, |this| {
17191731
this.parse_assoc_expr_with(1 + prec_let_scrutinee_needs_par(), None.into())
17201732
})?;
17211733
let span = lo.to(expr.span);

compiler/rustc_parse/src/parser/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ bitflags::bitflags! {
3636
struct Restrictions: u8 {
3737
const STMT_EXPR = 1 << 0;
3838
const NO_STRUCT_LITERAL = 1 << 1;
39+
const CONST_EXPR = 1 << 2;
3940
}
4041
}
4142

compiler/rustc_parse/src/parser/path.rs

+33-17
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,13 @@ impl<'a> Parser<'a> {
397397
while let Some(arg) = self.parse_angle_arg()? {
398398
args.push(arg);
399399
if !self.eat(&token::Comma) {
400+
if !self.token.kind.should_end_const_arg() {
401+
if self.handle_ambiguous_unbraced_const_arg(&mut args)? {
402+
// We've managed to (partially) recover, so continue trying to parse
403+
// arguments.
404+
continue;
405+
}
406+
}
400407
break;
401408
}
402409
}
@@ -476,41 +483,50 @@ impl<'a> Parser<'a> {
476483
Ok(self.mk_ty(span, ast::TyKind::Err))
477484
}
478485

486+
/// We do not permit arbitrary expressions as const arguments. They must be one of:
487+
/// - An expression surrounded in `{}`.
488+
/// - A literal.
489+
/// - A numeric literal prefixed by `-`.
490+
pub(super) fn expr_is_valid_const_arg(&self, expr: &P<rustc_ast::Expr>) -> bool {
491+
match &expr.kind {
492+
ast::ExprKind::Block(_, _) | ast::ExprKind::Lit(_) => true,
493+
ast::ExprKind::Unary(ast::UnOp::Neg, expr) => match &expr.kind {
494+
ast::ExprKind::Lit(_) => true,
495+
_ => false,
496+
},
497+
_ => false,
498+
}
499+
}
500+
479501
/// Parse a generic argument in a path segment.
480502
/// This does not include constraints, e.g., `Item = u8`, which is handled in `parse_angle_arg`.
481503
fn parse_generic_arg(&mut self) -> PResult<'a, Option<GenericArg>> {
504+
let start = self.token.span;
482505
let arg = if self.check_lifetime() && self.look_ahead(1, |t| !t.is_like_plus()) {
483506
// Parse lifetime argument.
484507
GenericArg::Lifetime(self.expect_lifetime())
485508
} else if self.check_const_arg() {
486509
// Parse const argument.
487-
let expr = if let token::OpenDelim(token::Brace) = self.token.kind {
510+
let value = if let token::OpenDelim(token::Brace) = self.token.kind {
488511
self.parse_block_expr(
489512
None,
490513
self.token.span,
491514
BlockCheckMode::Default,
492515
ast::AttrVec::new(),
493516
)?
494-
} else if self.token.is_ident() {
495-
// FIXME(const_generics): to distinguish between idents for types and consts,
496-
// we should introduce a GenericArg::Ident in the AST and distinguish when
497-
// lowering to the HIR. For now, idents for const args are not permitted.
498-
if self.token.is_bool_lit() {
499-
self.parse_literal_maybe_minus()?
500-
} else {
501-
let span = self.token.span;
502-
let msg = "identifiers may currently not be used for const generics";
503-
self.struct_span_err(span, msg).emit();
504-
let block = self.mk_block_err(span);
505-
self.mk_expr(span, ast::ExprKind::Block(block, None), ast::AttrVec::new())
506-
}
507517
} else {
508-
self.parse_literal_maybe_minus()?
518+
self.handle_unambiguous_unbraced_const_arg()?
509519
};
510-
GenericArg::Const(AnonConst { id: ast::DUMMY_NODE_ID, value: expr })
520+
GenericArg::Const(AnonConst { id: ast::DUMMY_NODE_ID, value })
511521
} else if self.check_type() {
512522
// Parse type argument.
513-
GenericArg::Type(self.parse_ty()?)
523+
match self.parse_ty() {
524+
Ok(ty) => GenericArg::Type(ty),
525+
Err(err) => {
526+
// Try to recover from possible `const` arg without braces.
527+
return self.recover_const_arg(start, err).map(Some);
528+
}
529+
}
514530
} else {
515531
return Ok(None);
516532
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
error: expressions must be enclosed in braces to be used as const generic arguments
2+
--> $DIR/closing-args-token.rs:11:9
3+
|
4+
LL | S::<5 + 2 >> 7>;
5+
| ^^^^^
6+
|
7+
help: enclose the `const` expression in braces
8+
|
9+
LL | S::<{ 5 + 2 } >> 7>;
10+
| ^ ^
11+
12+
error: comparison operators cannot be chained
13+
--> $DIR/closing-args-token.rs:11:16
14+
|
15+
LL | S::<5 + 2 >> 7>;
16+
| ^ ^
17+
|
18+
help: split the comparison into two
19+
|
20+
LL | S::<5 + 2 >> 7 && 7>;
21+
| ^^^^
22+
23+
error: comparison operators cannot be chained
24+
--> $DIR/closing-args-token.rs:17:20
25+
|
26+
LL | S::<{ 5 + 2 } >> 7>;
27+
| ^ ^
28+
|
29+
help: split the comparison into two
30+
|
31+
LL | S::<{ 5 + 2 } >> 7 && 7>;
32+
| ^^^^
33+
34+
error: expected expression, found `;`
35+
--> $DIR/closing-args-token.rs:22:16
36+
|
37+
LL | T::<0 >= 3>;
38+
| ^ expected expression
39+
40+
error: comparison operators cannot be chained
41+
--> $DIR/closing-args-token.rs:28:12
42+
|
43+
LL | T::<x >>= 2 > 0>;
44+
| ^^ ^
45+
|
46+
help: split the comparison into two
47+
|
48+
LL | T::<x >>= 2 && 2 > 0>;
49+
| ^^^^
50+
51+
error: aborting due to 5 previous errors
52+

0 commit comments

Comments
 (0)