Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide suggestions for const arguments missing braces #71592

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/librustc_ast/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,15 @@ pub enum AngleBracketedArg {
Constraint(AssocTyConstraint),
}

impl AngleBracketedArg {
pub fn span(&self) -> Span {
match self {
AngleBracketedArg::Arg(arg) => arg.span(),
AngleBracketedArg::Constraint(constraint) => constraint.span,
}
}
}

impl Into<Option<P<GenericArgs>>> for AngleBracketedArgs {
fn into(self) -> Option<P<GenericArgs>> {
Some(P(GenericArgs::AngleBracketed(self)))
Expand Down
7 changes: 7 additions & 0 deletions src/librustc_ast/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,13 @@ impl TokenKind {
_ => None,
}
}

pub fn should_end_const_arg(&self) -> bool {
match self {
Gt | Ge | BinOp(Shr) | BinOpEq(Shr) => true,
_ => false,
}
}
}

impl Token {
Expand Down
67 changes: 66 additions & 1 deletion src/librustc_ast_lowering/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ use rustc_ast_pretty::pprust;
use rustc_data_structures::captures::Captures;
use rustc_data_structures::fx::FxHashSet;
use rustc_data_structures::sync::Lrc;
use rustc_errors::struct_span_err;
use rustc_errors::{struct_span_err, Applicability};
use rustc_hir as hir;
use rustc_hir::def::{DefKind, Namespace, PartialRes, PerNS, Res};
use rustc_hir::def_id::{DefId, DefIdMap, LocalDefId, CRATE_DEF_INDEX};
Expand Down Expand Up @@ -1136,6 +1136,71 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
}
}
}
// Possible `a + b` expression that should be surrounded in braces but was parsed
// as trait bounds in a trait object. Suggest surrounding with braces.
if let TyKind::TraitObject(ref bounds, TraitObjectSyntax::None) = ty.kind {
// We cannot disambiguate multi-segment paths right now as that requires type
// checking.
let const_expr_without_braces = bounds.iter().all(|bound| match bound {
GenericBound::Trait(
PolyTraitRef {
bound_generic_params,
trait_ref: TraitRef { path, .. },
..
},
TraitBoundModifier::None,
) if bound_generic_params.is_empty()
&& path.segments.len() == 1
&& path.segments[0].args.is_none() =>
{
let part_res = self.resolver.get_partial_res(path.segments[0].id);
match part_res.map(|r| r.base_res()) {
Some(res) => {
!res.matches_ns(Namespace::TypeNS)
&& res.matches_ns(Namespace::ValueNS)
}
None => true,
}
}
_ => false,
});
if const_expr_without_braces {
self.sess.struct_span_err(ty.span, "likely `const` expression parsed as trait bounds")
.span_label(ty.span, "parsed as trait bounds but traits weren't found")
.multipart_suggestion(
"if you meant to write a `const` expression, surround the expression with braces",
vec![
(ty.span.shrink_to_lo(), "{ ".to_string()),
(ty.span.shrink_to_hi(), " }".to_string()),
],
Applicability::MachineApplicable,
)
.emit();

let parent_def_id = self.current_hir_id_owner.last().unwrap().0;
let node_id = self.resolver.next_node_id();
// Add a definition for the in-band const def.
self.resolver.definitions().create_def_with_parent(
parent_def_id,
node_id,
DefPathData::AnonConst,
ExpnId::root(),
ty.span,
);

let path_expr = Expr {
id: ty.id,
kind: ExprKind::Err,
span: ty.span,
attrs: AttrVec::new(),
};
let value = self.with_new_scopes(|this| hir::AnonConst {
hir_id: this.lower_node_id(node_id),
body: this.lower_const_body(path_expr.span, Some(&path_expr)),
});
return GenericArg::Const(ConstArg { value, span: ty.span });
}
}
GenericArg::Type(self.lower_ty_direct(&ty, itctx))
}
ast::GenericArg::Const(ct) => GenericArg::Const(ConstArg {
Expand Down
1 change: 1 addition & 0 deletions src/librustc_middle/ty/sty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2275,6 +2275,7 @@ impl<'tcx> Const<'tcx> {
let name = tcx.hir().name(hir_id);
ty::ConstKind::Param(ty::ParamConst::new(index, name))
}
ExprKind::Err => ty::ConstKind::Error,
_ => ty::ConstKind::Unevaluated(
def_id.to_def_id(),
InternalSubsts::identity_for_item(tcx, def_id.to_def_id()),
Expand Down
18 changes: 16 additions & 2 deletions src/librustc_parse/parser/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -329,8 +329,20 @@ impl<'a> Parser<'a> {
/// The method does not advance the current token.
///
/// Also performs recovery for `and` / `or` which are mistaken for `&&` and `||` respectively.
fn check_assoc_op(&self) -> Option<Spanned<AssocOp>> {
crate fn check_assoc_op(&self) -> Option<Spanned<AssocOp>> {
let (op, span) = match (AssocOp::from_token(&self.token), self.token.ident()) {
// When parsing const expressions, stop parsing when encountering `>`.
(
Some(
AssocOp::ShiftRight
| AssocOp::Greater
| AssocOp::GreaterEqual
| AssocOp::AssignOp(token::BinOpToken::Shr),
),
_,
) if self.restrictions.contains(Restrictions::CONST_EXPR) => {
return None;
}
(Some(op), _) => (op, self.token.span),
(None, Some((Ident { name: sym::and, span }, false))) => {
self.error_bad_logical_op("and", "&&", "conjunction");
Expand Down Expand Up @@ -1585,7 +1597,9 @@ impl<'a> Parser<'a> {
let lo = self.prev_token.span;
let pat = self.parse_top_pat(GateOr::No)?;
self.expect(&token::Eq)?;
let expr = self.with_res(Restrictions::NO_STRUCT_LITERAL, |this| {
// `self.restrictions |` below is to account for `CONST_EXPR` so that we can correctly
// parse `let` expressions in `const` arguments.
let expr = self.with_res(self.restrictions | Restrictions::NO_STRUCT_LITERAL, |this| {
this.parse_assoc_expr_with(1 + prec_let_scrutinee_needs_par(), None.into())
})?;
let span = lo.to(expr.span);
Expand Down
1 change: 1 addition & 0 deletions src/librustc_parse/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ bitflags::bitflags! {
struct Restrictions: u8 {
const STMT_EXPR = 1 << 0;
const NO_STRUCT_LITERAL = 1 << 1;
const CONST_EXPR = 1 << 2;
}
}

Expand Down
164 changes: 148 additions & 16 deletions src/librustc_parse/parser/path.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use super::ty::{AllowPlus, RecoverQPath};
use super::{Parser, TokenType};
use super::{Parser, Restrictions, TokenType};
use crate::maybe_whole;
use rustc_ast::ast::{self, AngleBracketedArg, AngleBracketedArgs, GenericArg, ParenthesizedArgs};
use rustc_ast::ast::{AnonConst, AssocTyConstraint, AssocTyConstraintKind, BlockCheckMode};
use rustc_ast::ast::{Ident, Path, PathSegment, QSelf};
use rustc_ast::ptr::P;
use rustc_ast::token::{self, Token};
use rustc_errors::{pluralize, Applicability, PResult};
use rustc_ast::util::parser::AssocOp;
use rustc_errors::{pluralize, Applicability, DiagnosticBuilder, PResult};
use rustc_span::source_map::{BytePos, Span};
use rustc_span::symbol::{kw, sym};

Expand Down Expand Up @@ -392,12 +393,112 @@ impl<'a> Parser<'a> {
while let Some(arg) = self.parse_angle_arg()? {
args.push(arg);
if !self.eat(&token::Comma) {
if self.token.kind.should_end_const_arg() {
// We will correctly parse a closing `>`, exit.
} else {
// Try to recover from possible `const` arg without braces.
let arg = args.pop().unwrap();
// FIXME: for some reason using `unexpected` or `expected_one_of_not_found` has
// adverse side-effects to subsequent errors and seems to advance the parser.
// We are causing this error here exclusively in case that a `const` expression
// could be recovered from the current parser state, even if followed by more
// arguments after a comma.
let mut err = self.struct_span_err(
self.token.span,
&format!(
"expected one of `,` or `>`, found {}",
super::token_descr(&self.token)
),
);
err.span_label(self.token.span, "expected one of `,` or `>`");
match self.recover_const_arg(arg.span(), err) {
Ok(arg) => {
args.push(AngleBracketedArg::Arg(arg));
if self.eat(&token::Comma) {
continue;
}
}
Err(mut err) => {
args.push(arg);
// We will emit a more generic error later.
err.delay_as_bug();
}
}
}
break;
}
}
Ok(args)
}

/// Try to recover from possible `const` arg without braces.
///
/// When encountering code like `foo::< bar + 3 >` or `foo::< bar - baz >` we suggest
/// `foo::<{ bar + 3 }>` and `foo::<{ bar - baz }>` respectively. We only provide a suggestion
/// when we have a high degree of certainty that the resulting expression would be well formed.
pub fn recover_const_arg(
&mut self,
start: Span,
mut err: DiagnosticBuilder<'a>,
) -> PResult<'a, GenericArg> {
let is_op = AssocOp::from_token(&self.token)
.and_then(|op| {
if let AssocOp::Greater
| AssocOp::Less
| AssocOp::ShiftRight
| AssocOp::GreaterEqual
| AssocOp::Assign // Don't recover from `foo::<bar = baz>`
| AssocOp::AssignOp(_) = op
{
None
} else {
Some(op)
}
})
.is_some();
// This will be true when a trait object type `Foo +` or a path which was a `const fn` with
// type params has been parsed.
let was_op =
matches!(self.prev_token.kind, token::BinOp(token::Plus | token::Shr) | token::Gt);
if !is_op && !was_op {
// We perform these checks and early return to avoid taking a snapshot unnecessarily.
return Err(err);
}
let snapshot = self.clone();
if is_op {
self.bump();
}
match self.parse_expr_res(Restrictions::CONST_EXPR, None) {
Ok(expr) => {
if token::Comma == self.token.kind || self.token.kind.should_end_const_arg() {
// Avoid the following output by checking that we consumed a full const arg:
// help: to write a `const` expression, surround it with braces for it to
// be unambiguous
// |
// LL | let sr: Vec<{ (u32, _, _) = vec![] };
// | ^ ^
err.multipart_suggestion(
"to write a `const` expression, surround it with braces for it to be \
unambiguous",
vec![
(start.shrink_to_lo(), "{ ".to_string()),
(expr.span.shrink_to_hi(), " }".to_string()),
],
Applicability::MaybeIncorrect,
);
let value = self.mk_expr_err(start.to(expr.span));
err.emit();
return Ok(GenericArg::Const(AnonConst { id: ast::DUMMY_NODE_ID, value }));
}
}
Err(mut err) => {
err.cancel();
}
}
*self = snapshot;
Err(err)
}

/// Parses a single argument in the angle arguments `<...>` of a path segment.
fn parse_angle_arg(&mut self) -> PResult<'a, Option<AngleBracketedArg>> {
if self.check_ident() && self.look_ahead(1, |t| matches!(t.kind, token::Eq | token::Colon))
Expand Down Expand Up @@ -474,41 +575,72 @@ impl<'a> Parser<'a> {
/// Parse a generic argument in a path segment.
/// This does not include constraints, e.g., `Item = u8`, which is handled in `parse_angle_arg`.
fn parse_generic_arg(&mut self) -> PResult<'a, Option<GenericArg>> {
let start = self.token.span;
let arg = if self.check_lifetime() && self.look_ahead(1, |t| !t.is_like_plus()) {
// Parse lifetime argument.
GenericArg::Lifetime(self.expect_lifetime())
} else if self.check_const_arg() {
// Parse const argument.
let expr = if let token::OpenDelim(token::Brace) = self.token.kind {
let value = if let token::OpenDelim(token::Brace) = self.token.kind {
self.parse_block_expr(
None,
self.token.span,
BlockCheckMode::Default,
ast::AttrVec::new(),
)?
} else if self.token.is_ident() {
} else {
// FIXME(const_generics): to distinguish between idents for types and consts,
// we should introduce a GenericArg::Ident in the AST and distinguish when
// lowering to the HIR. For now, idents for const args are not permitted.
if self.token.is_bool_lit() {
self.parse_literal_maybe_minus()?
} else {
let span = self.token.span;
let msg = "identifiers may currently not be used for const generics";
self.struct_span_err(span, msg).emit();
let block = self.mk_block_err(span);
self.mk_expr(span, ast::ExprKind::Block(block, None), ast::AttrVec::new())
let start = self.token.span;
let expr =
self.parse_expr_res(Restrictions::CONST_EXPR, None).map_err(|mut err| {
err.span_label(
start.shrink_to_lo(),
"while parsing a `const` argument starting here",
);
err
})?;
let valid_const = match &expr.kind {
ast::ExprKind::Block(_, _) | ast::ExprKind::Lit(_) => true,
ast::ExprKind::Unary(ast::UnOp::Neg, expr) => match &expr.kind {
ast::ExprKind::Lit(_) => true,
_ => false,
},
_ => false,
};
if !valid_const {
self.struct_span_err(
expr.span,
"`const` generic expressions without braces are not supported",
)
.note("only literals are supported as `const` generic without braces")
.multipart_suggestion(
"surround `const` expressions with braces",
vec![
(expr.span.shrink_to_lo(), "{ ".to_string()),
(expr.span.shrink_to_hi(), " }".to_string()),
],
Applicability::MachineApplicable,
)
.emit();
}
} else {
self.parse_literal_maybe_minus()?
expr
};
GenericArg::Const(AnonConst { id: ast::DUMMY_NODE_ID, value: expr })
GenericArg::Const(AnonConst { id: ast::DUMMY_NODE_ID, value })
} else if self.check_type() {
// Parse type argument.
GenericArg::Type(self.parse_ty()?)
match self.parse_ty() {
Ok(ty) => GenericArg::Type(ty),
Err(err) => {
// Try to recover from possible `const` arg without braces.
return self.recover_const_arg(start, err).map(Some);
}
}
} else {
return Ok(None);
};
// FIXME: recover from const expressions without braces that require them.
Ok(Some(arg))
}
}
Loading