Skip to content

Commit ac14540

Browse files
varkorestebank
andcommitted
Suggest expressions that look like const generic arguments should be enclosed in brackets
Co-Authored-By: Esteban Kuber <[email protected]>
1 parent 1d27267 commit ac14540

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

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)