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

syntax: Better recovery for $ty::AssocItem and ty!()::AssocItem #59058

Merged
merged 3 commits into from
Mar 23, 2019
Merged
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
170 changes: 82 additions & 88 deletions src/libsyntax/parse/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,23 +118,22 @@ enum BlockMode {
/// `token::Interpolated` tokens.
macro_rules! maybe_whole_expr {
($p:expr) => {
if let token::Interpolated(nt) = $p.token.clone() {
match *nt {
token::NtExpr(ref e) | token::NtLiteral(ref e) => {
if let token::Interpolated(nt) = &$p.token {
match &**nt {
token::NtExpr(e) | token::NtLiteral(e) => {
let e = e.clone();
$p.bump();
return Ok((*e).clone());
return Ok(e);
}
token::NtPath(ref path) => {
token::NtPath(path) => {
let path = path.clone();
$p.bump();
let span = $p.span;
let kind = ExprKind::Path(None, (*path).clone());
return Ok($p.mk_expr(span, kind, ThinVec::new()));
return Ok($p.mk_expr($p.span, ExprKind::Path(None, path), ThinVec::new()));
}
token::NtBlock(ref block) => {
token::NtBlock(block) => {
let block = block.clone();
$p.bump();
let span = $p.span;
let kind = ExprKind::Block((*block).clone(), None);
return Ok($p.mk_expr(span, kind, ThinVec::new()));
return Ok($p.mk_expr($p.span, ExprKind::Block(block, None), ThinVec::new()));
}
_ => {},
};
Expand All @@ -145,15 +144,31 @@ macro_rules! maybe_whole_expr {
/// As maybe_whole_expr, but for things other than expressions
macro_rules! maybe_whole {
($p:expr, $constructor:ident, |$x:ident| $e:expr) => {
if let token::Interpolated(nt) = $p.token.clone() {
if let token::$constructor($x) = (*nt).clone() {
if let token::Interpolated(nt) = &$p.token {
if let token::$constructor(x) = &**nt {
let $x = x.clone();
$p.bump();
return Ok($e);
}
}
};
}

/// If the next tokens are ill-formed `$ty::` recover them as `<$ty>::`.
macro_rules! maybe_recover_from_interpolated_ty_qpath {
($self: expr, $allow_qpath_recovery: expr) => {
if $allow_qpath_recovery && $self.look_ahead(1, |t| t == &token::ModSep) {
if let token::Interpolated(nt) = &$self.token {
if let token::NtTy(ty) = &**nt {
let ty = ty.clone();
$self.bump();
return $self.maybe_recover_from_bad_qpath_stage_2($self.prev_span, ty);
}
}
}
}
}

fn maybe_append(mut lhs: Vec<Attribute>, mut rhs: Option<Vec<Attribute>>) -> Vec<Attribute> {
if let Some(ref mut rhs) = rhs {
lhs.append(rhs);
Expand All @@ -172,48 +187,38 @@ enum PrevTokenKind {
Other,
}

trait RecoverQPath: Sized {
trait RecoverQPath: Sized + 'static {
const PATH_STYLE: PathStyle = PathStyle::Expr;
fn to_ty(&self) -> Option<P<Ty>>;
fn to_recovered(&self, qself: Option<QSelf>, path: ast::Path) -> Self;
fn to_string(&self) -> String;
fn recovered(qself: Option<QSelf>, path: ast::Path) -> Self;
}

impl RecoverQPath for Ty {
const PATH_STYLE: PathStyle = PathStyle::Type;
fn to_ty(&self) -> Option<P<Ty>> {
Some(P(self.clone()))
}
fn to_recovered(&self, qself: Option<QSelf>, path: ast::Path) -> Self {
Self { span: path.span, node: TyKind::Path(qself, path), id: self.id }
}
fn to_string(&self) -> String {
pprust::ty_to_string(self)
fn recovered(qself: Option<QSelf>, path: ast::Path) -> Self {
Self { span: path.span, node: TyKind::Path(qself, path), id: ast::DUMMY_NODE_ID }
}
}

impl RecoverQPath for Pat {
fn to_ty(&self) -> Option<P<Ty>> {
self.to_ty()
}
fn to_recovered(&self, qself: Option<QSelf>, path: ast::Path) -> Self {
Self { span: path.span, node: PatKind::Path(qself, path), id: self.id }
}
fn to_string(&self) -> String {
pprust::pat_to_string(self)
fn recovered(qself: Option<QSelf>, path: ast::Path) -> Self {
Self { span: path.span, node: PatKind::Path(qself, path), id: ast::DUMMY_NODE_ID }
}
}

impl RecoverQPath for Expr {
fn to_ty(&self) -> Option<P<Ty>> {
self.to_ty()
}
fn to_recovered(&self, qself: Option<QSelf>, path: ast::Path) -> Self {
fn recovered(qself: Option<QSelf>, path: ast::Path) -> Self {
Self { span: path.span, node: ExprKind::Path(qself, path),
id: self.id, attrs: self.attrs.clone() }
}
fn to_string(&self) -> String {
pprust::expr_to_string(self)
attrs: ThinVec::new(), id: ast::DUMMY_NODE_ID }
}
}

Expand Down Expand Up @@ -1651,6 +1656,7 @@ impl<'a> Parser<'a> {

fn parse_ty_common(&mut self, allow_plus: bool, allow_qpath_recovery: bool,
allow_c_variadic: bool) -> PResult<'a, P<Ty>> {
maybe_recover_from_interpolated_ty_qpath!(self, allow_qpath_recovery);
maybe_whole!(self, NtTy, |x| x);

let lo = self.span;
Expand Down Expand Up @@ -1802,14 +1808,12 @@ impl<'a> Parser<'a> {
};

let span = lo.to(self.prev_span);
let ty = Ty { node, span, id: ast::DUMMY_NODE_ID };
let ty = P(Ty { node, span, id: ast::DUMMY_NODE_ID });

// Try to recover from use of `+` with incorrect priority.
self.maybe_report_ambiguous_plus(allow_plus, impl_dyn_multi, &ty);
self.maybe_recover_from_bad_type_plus(allow_plus, &ty)?;
let ty = self.maybe_recover_from_bad_qpath(ty, allow_qpath_recovery)?;

Ok(P(ty))
self.maybe_recover_from_bad_qpath(ty, allow_qpath_recovery)
}

fn parse_remaining_bounds(&mut self, generic_params: Vec<GenericParam>, path: ast::Path,
Expand Down Expand Up @@ -1880,36 +1884,40 @@ impl<'a> Parser<'a> {
Ok(())
}

// Try to recover from associated item paths like `[T]::AssocItem`/`(T, U)::AssocItem`.
fn maybe_recover_from_bad_qpath<T: RecoverQPath>(&mut self, base: T, allow_recovery: bool)
-> PResult<'a, T> {
/// Try to recover from associated item paths like `[T]::AssocItem`/`(T, U)::AssocItem`.
/// Attempt to convert the base expression/pattern/type into a type, parse the `::AssocItem`
/// tail, and combine them into a `<Ty>::AssocItem` expression/pattern/type.
fn maybe_recover_from_bad_qpath<T: RecoverQPath>(&mut self, base: P<T>, allow_recovery: bool)
-> PResult<'a, P<T>> {
// Do not add `::` to expected tokens.
if !allow_recovery || self.token != token::ModSep {
return Ok(base);
if allow_recovery && self.token == token::ModSep {
if let Some(ty) = base.to_ty() {
return self.maybe_recover_from_bad_qpath_stage_2(ty.span, ty);
}
}
let ty = match base.to_ty() {
Some(ty) => ty,
None => return Ok(base),
};
Ok(base)
}

self.bump(); // `::`
let mut segments = Vec::new();
self.parse_path_segments(&mut segments, T::PATH_STYLE, true)?;
/// Given an already parsed `Ty` parse the `::AssocItem` tail and
/// combine them into a `<Ty>::AssocItem` expression/pattern/type.
fn maybe_recover_from_bad_qpath_stage_2<T: RecoverQPath>(&mut self, ty_span: Span, ty: P<Ty>)
petrochenkov marked this conversation as resolved.
Show resolved Hide resolved
-> PResult<'a, P<T>> {
self.expect(&token::ModSep)?;

let span = ty.span.to(self.prev_span);
let path_span = span.to(span); // use an empty path since `position` == 0
let recovered = base.to_recovered(
Some(QSelf { ty, path_span, position: 0 }),
ast::Path { segments, span },
);
let mut path = ast::Path { segments: Vec::new(), span: syntax_pos::DUMMY_SP };
self.parse_path_segments(&mut path.segments, T::PATH_STYLE, true)?;
path.span = ty_span.to(self.prev_span);

let ty_str = self.sess.source_map().span_to_snippet(ty_span)
.unwrap_or_else(|_| pprust::ty_to_string(&ty));
self.diagnostic()
.struct_span_err(span, "missing angle brackets in associated item path")
.struct_span_err(path.span, "missing angle brackets in associated item path")
.span_suggestion( // this is a best-effort recovery
span, "try", recovered.to_string(), Applicability::MaybeIncorrect
path.span, "try", format!("<{}>::{}", ty_str, path), Applicability::MaybeIncorrect
).emit();

Ok(recovered)
let path_span = ty_span.shrink_to_hi(); // use an empty path since `position` == 0
Ok(P(T::recovered(Some(QSelf { ty, path_span, position: 0 }), path)))
}

fn parse_borrowed_pointee(&mut self) -> PResult<'a, TyKind> {
Expand Down Expand Up @@ -2574,15 +2582,6 @@ impl<'a> Parser<'a> {
ExprKind::AssignOp(binop, lhs, rhs)
}

pub fn mk_mac_expr(&mut self, span: Span, m: Mac_, attrs: ThinVec<Attribute>) -> P<Expr> {
P(Expr {
id: ast::DUMMY_NODE_ID,
node: ExprKind::Mac(source_map::Spanned {node: m, span: span}),
span,
attrs,
})
}

fn expect_delimited_token_tree(&mut self) -> PResult<'a, (MacDelimiter, TokenStream)> {
let delim = match self.token {
token::OpenDelim(delim) => delim,
Expand Down Expand Up @@ -2612,6 +2611,7 @@ impl<'a> Parser<'a> {
/// N.B., this does not parse outer attributes, and is private because it only works
/// correctly if called from `parse_dot_or_call_expr()`.
fn parse_bottom_expr(&mut self) -> PResult<'a, P<Expr>> {
maybe_recover_from_interpolated_ty_qpath!(self, true);
maybe_whole_expr!(self);

// Outer attributes are already parsed and will be
Expand Down Expand Up @@ -2826,29 +2826,23 @@ impl<'a> Parser<'a> {
db.note("variable declaration using `let` is a statement");
return Err(db);
} else if self.token.is_path_start() {
let pth = self.parse_path(PathStyle::Expr)?;
let path = self.parse_path(PathStyle::Expr)?;

// `!`, as an operator, is prefix, so we know this isn't that
if self.eat(&token::Not) {
// MACRO INVOCATION expression
let (delim, tts) = self.expect_delimited_token_tree()?;
let hi = self.prev_span;
let node = Mac_ { path: pth, tts, delim };
return Ok(self.mk_mac_expr(lo.to(hi), node, attrs))
}
if self.check(&token::OpenDelim(token::Brace)) {
hi = self.prev_span;
ex = ExprKind::Mac(respan(lo.to(hi), Mac_ { path, tts, delim }));
} else if self.check(&token::OpenDelim(token::Brace)) &&
!self.restrictions.contains(Restrictions::NO_STRUCT_LITERAL) {
// This is a struct literal, unless we're prohibited
// from parsing struct literals here.
let prohibited = self.restrictions.contains(
Restrictions::NO_STRUCT_LITERAL
);
if !prohibited {
return self.parse_struct_expr(lo, pth, attrs);
}
return self.parse_struct_expr(lo, path, attrs);
} else {
hi = path.span;
ex = ExprKind::Path(None, path);
}

hi = pth.span;
ex = ExprKind::Path(None, pth);
} else {
if !self.unclosed_delims.is_empty() && self.check(&token::Semi) {
// Don't complain about bare semicolons after unclosed braces
Expand Down Expand Up @@ -2883,10 +2877,8 @@ impl<'a> Parser<'a> {
}
}

let expr = Expr { node: ex, span: lo.to(hi), id: ast::DUMMY_NODE_ID, attrs };
let expr = self.maybe_recover_from_bad_qpath(expr, true)?;

return Ok(P(expr));
let expr = self.mk_expr(lo.to(hi), ex, attrs);
self.maybe_recover_from_bad_qpath(expr, true)
}

fn parse_struct_expr(&mut self, lo: Span, pth: ast::Path, mut attrs: ThinVec<Attribute>)
Expand Down Expand Up @@ -4581,6 +4573,7 @@ impl<'a> Parser<'a> {
allow_range_pat: bool,
expected: Option<&'static str>,
) -> PResult<'a, P<Pat>> {
maybe_recover_from_interpolated_ty_qpath!(self, true);
maybe_whole!(self, NtPat, |x| x);

let lo = self.span;
Expand Down Expand Up @@ -4756,7 +4749,7 @@ impl<'a> Parser<'a> {
}
}

let pat = Pat { node: pat, span: lo.to(self.prev_span), id: ast::DUMMY_NODE_ID };
let pat = P(Pat { node: pat, span: lo.to(self.prev_span), id: ast::DUMMY_NODE_ID });
let pat = self.maybe_recover_from_bad_qpath(pat, true)?;

if !allow_range_pat {
Expand All @@ -4782,7 +4775,7 @@ impl<'a> Parser<'a> {
}
}

Ok(P(pat))
Ok(pat)
}

/// Parses `ident` or `ident @ pat`.
Expand Down Expand Up @@ -5250,7 +5243,8 @@ impl<'a> Parser<'a> {
self.warn_missing_semicolon();
StmtKind::Mac(P((mac, style, attrs.into())))
} else {
let e = self.mk_mac_expr(lo.to(hi), mac.node, ThinVec::new());
let e = self.mk_expr(mac.span, ExprKind::Mac(mac), ThinVec::new());
let e = self.maybe_recover_from_bad_qpath(e, true)?;
let e = self.parse_dot_or_call_expr_with(e, lo, attrs.into())?;
let e = self.parse_assoc_expr_with(0, LhsExpr::AlreadyParsed(e))?;
StmtKind::Expr(e)
Expand Down
16 changes: 16 additions & 0 deletions src/test/ui/did_you_mean/bad-assoc-expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,19 @@ fn main() {
10 + (u8)::clone(&0);
//~^ ERROR missing angle brackets in associated item path
}

macro_rules! expr {
($ty: ty) => ($ty::clone(&0))
//~^ ERROR missing angle brackets in associated item path
}
macro_rules! ty {
() => (u8)
}

fn check_macros() {
expr!(u8);
let _ = ty!()::clone(&0);
//~^ ERROR missing angle brackets in associated item path
ty!()::clone(&0);
//~^ ERROR missing angle brackets in associated item path
}
23 changes: 22 additions & 1 deletion src/test/ui/did_you_mean/bad-assoc-expr.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,26 @@ error: missing angle brackets in associated item path
LL | 10 + (u8)::clone(&0);
| ^^^^^^^^^^^ help: try: `<(u8)>::clone`

error: aborting due to 6 previous errors
error: missing angle brackets in associated item path
--> $DIR/bad-assoc-expr.rs:32:13
|
LL | let _ = ty!()::clone(&0);
| ^^^^^^^^^^^^ help: try: `<ty!()>::clone`

error: missing angle brackets in associated item path
--> $DIR/bad-assoc-expr.rs:34:5
|
LL | ty!()::clone(&0);
| ^^^^^^^^^^^^ help: try: `<ty!()>::clone`

error: missing angle brackets in associated item path
--> $DIR/bad-assoc-expr.rs:23:19
|
LL | ($ty: ty) => ($ty::clone(&0))
| ^^^^^^^^^^ help: try: `<$ty>::clone`
...
LL | expr!(u8);
| ---------- in this macro invocation

error: aborting due to 9 previous errors

18 changes: 18 additions & 0 deletions src/test/ui/did_you_mean/bad-assoc-pat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,21 @@ fn main() {
//~| ERROR no associated item named `AssocItem` found for type `(u8,)` in the current scope
}
}

macro_rules! pat {
($ty: ty) => ($ty::AssocItem)
//~^ ERROR missing angle brackets in associated item path
//~| ERROR no associated item named `AssocItem` found for type `u8` in the current scope
}
macro_rules! ty {
() => (u8)
}

fn check_macros() {
match 0u8 {
pat!(u8) => {}
ty!()::AssocItem => {}
//~^ ERROR missing angle brackets in associated item path
//~| ERROR no associated item named `AssocItem` found for type `u8` in the current scope
}
}
Loading