about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2019-12-21 11:05:03 +0000
committerbors <bors@rust-lang.org>2019-12-21 11:05:03 +0000
commitc64eecf4d0907095928fb36fd3a1dd5fb2d9ff06 (patch)
tree073a8038727414aed13f54622fce43ca63990ca6
parent9ff30a7810c586819a78188c173a7b74adbb9730 (diff)
parent621661f8a63f2118f3add5c3d686d9a2b6f62e5e (diff)
downloadrust-c64eecf4d0907095928fb36fd3a1dd5fb2d9ff06.tar.gz
rust-c64eecf4d0907095928fb36fd3a1dd5fb2d9ff06.zip
Auto merge of #66994 - Centril:stmt-polish, r=estebank
refactor expr & stmt parsing + improve recovery

Summary of important changes (best read commit-by-commit, ignoring whitespace changes):

- `AttrVec` is introduces as an alias for `ThinVec<Attribute>`
- `parse_expr_bottom` and `parse_stmt` are thoroughly refactored.
- Extract diagnostics logic for `vec![...]` in a pattern context.
- Recovery is added for `do catch { ... }`
- Recovery is added for `'label: non_block_expr`
- Recovery is added for `var $local`, `auto $local`, and `mut $local`. Fixes #65257.
- Recovery is added for `e1 and e2` and `e1 or e2`.
- ~~`macro_legacy_warnings` is turned into an error (has been a warning for 3 years!)~~
- Fixes #63396 by forward-porting #64105 which now works thanks to added recovery.
- `ui-fulldeps/ast_stmt_expr_attr.rs` is turned into UI and pretty tests.
- Recovery is fixed for `#[attr] if expr {}`

r? @estebank
-rw-r--r--src/librustc/hir/lowering.rs7
-rw-r--r--src/librustc/hir/lowering/expr.rs18
-rw-r--r--src/librustc/hir/lowering/item.rs13
-rw-r--r--src/librustc/hir/mod.rs7
-rw-r--r--src/librustc_interface/util.rs7
-rw-r--r--src/librustc_parse/lib.rs1
-rw-r--r--src/librustc_parse/parser/attr.rs23
-rw-r--r--src/librustc_parse/parser/diagnostics.rs65
-rw-r--r--src/librustc_parse/parser/expr.rs817
-rw-r--r--src/librustc_parse/parser/item.rs7
-rw-r--r--src/librustc_parse/parser/mod.rs71
-rw-r--r--src/librustc_parse/parser/pat.rs11
-rw-r--r--src/librustc_parse/parser/path.rs3
-rw-r--r--src/librustc_parse/parser/stmt.rs448
-rw-r--r--src/libsyntax/ast.rs19
-rw-r--r--src/libsyntax/attr/mod.rs5
-rw-r--r--src/libsyntax/lib.rs1
-rw-r--r--src/libsyntax/mut_visit.rs3
-rw-r--r--src/libsyntax_expand/base.rs3
-rw-r--r--src/libsyntax_expand/build.rs15
-rw-r--r--src/libsyntax_expand/mbe/macro_rules.rs46
-rw-r--r--src/libsyntax_expand/placeholders.rs5
-rw-r--r--src/libsyntax_ext/asm.rs3
-rw-r--r--src/libsyntax_ext/concat_idents.rs4
-rw-r--r--src/libsyntax_ext/deriving/debug.rs4
-rw-r--r--src/libsyntax_ext/deriving/generic/mod.rs5
-rw-r--r--src/libsyntax_pos/symbol.rs1
-rw-r--r--src/test/pretty/ast-stmt-expr-attr.rs175
-rw-r--r--src/test/ui-fulldeps/ast_stmt_expr_attr.rs311
-rw-r--r--src/test/ui/did_you_mean/issue-54109-and_instead_of_ampersands.rs31
-rw-r--r--src/test/ui/did_you_mean/issue-54109-and_instead_of_ampersands.stderr95
-rw-r--r--src/test/ui/parser/attr-stmt-expr-attr-bad-2.rs2
-rw-r--r--src/test/ui/parser/attr-stmt-expr-attr-bad-2.stderr8
-rw-r--r--src/test/ui/parser/attr-stmt-expr-attr-bad-3.rs2
-rw-r--r--src/test/ui/parser/attr-stmt-expr-attr-bad-3.stderr8
-rw-r--r--src/test/ui/parser/attr-stmt-expr-attr-bad.rs107
-rw-r--r--src/test/ui/parser/attr-stmt-expr-attr-bad.stderr390
-rw-r--r--src/test/ui/parser/do-catch-suggests-try.rs7
-rw-r--r--src/test/ui/parser/do-catch-suggests-try.stderr17
-rw-r--r--src/test/ui/parser/issue-65257-invalid-var-decl-recovery.rs21
-rw-r--r--src/test/ui/parser/issue-65257-invalid-var-decl-recovery.stderr67
-rw-r--r--src/test/ui/parser/recover-labeled-non-block-expr.rs5
-rw-r--r--src/test/ui/parser/recover-labeled-non-block-expr.stderr17
-rw-r--r--src/test/ui/parser/recovery-attr-on-if.rs9
-rw-r--r--src/test/ui/parser/recovery-attr-on-if.stderr35
-rw-r--r--src/test/ui/parser/stmt_expr_attrs_placement.rs22
-rw-r--r--src/test/ui/parser/stmt_expr_attrs_placement.stderr10
47 files changed, 1764 insertions, 1187 deletions
diff --git a/src/librustc/hir/lowering.rs b/src/librustc/hir/lowering.rs
index 3f8085f2344..6b83788298e 100644
--- a/src/librustc/hir/lowering.rs
+++ b/src/librustc/hir/lowering.rs
@@ -53,7 +53,6 @@ use crate::util::nodemap::{DefIdMap, NodeMap};
 use errors::Applicability;
 use rustc_data_structures::fx::FxHashSet;
 use rustc_index::vec::IndexVec;
-use rustc_data_structures::thin_vec::ThinVec;
 use rustc_data_structures::sync::Lrc;
 
 use std::collections::BTreeMap;
@@ -1205,7 +1204,7 @@ impl<'a> LoweringContext<'a> {
                                 id: ty.id,
                                 kind: ExprKind::Path(qself.clone(), path.clone()),
                                 span: ty.span,
-                                attrs: ThinVec::new(),
+                                attrs: AttrVec::new(),
                             };
 
                             let ct = self.with_new_scopes(|this| {
@@ -2751,7 +2750,7 @@ impl<'a> LoweringContext<'a> {
     /// has no attributes and is not targeted by a `break`.
     fn lower_block_expr(&mut self, b: &Block) -> hir::Expr {
         let block = self.lower_block(b, false);
-        self.expr_block(block, ThinVec::new())
+        self.expr_block(block, AttrVec::new())
     }
 
     fn lower_pat(&mut self, p: &Pat) -> P<hir::Pat> {
@@ -3102,7 +3101,7 @@ impl<'a> LoweringContext<'a> {
 
     fn stmt_let_pat(
         &mut self,
-        attrs: ThinVec<Attribute>,
+        attrs: AttrVec,
         span: Span,
         init: Option<P<hir::Expr>>,
         pat: P<hir::Pat>,
diff --git a/src/librustc/hir/lowering/expr.rs b/src/librustc/hir/lowering/expr.rs
index f8465baeb13..04031710dc5 100644
--- a/src/librustc/hir/lowering/expr.rs
+++ b/src/librustc/hir/lowering/expr.rs
@@ -1318,8 +1318,7 @@ impl LoweringContext<'_> {
         &mut self,
         span: Span,
         expr: P<hir::Expr>,
-        attrs: ThinVec<Attribute>
-    ) -> hir::Expr {
+        attrs: AttrVec) -> hir::Expr {
         self.expr(span, hir::ExprKind::DropTemps(expr), attrs)
     }
 
@@ -1333,7 +1332,7 @@ impl LoweringContext<'_> {
         self.expr(span, hir::ExprKind::Match(arg, arms, source), ThinVec::new())
     }
 
-    fn expr_break(&mut self, span: Span, attrs: ThinVec<Attribute>) -> P<hir::Expr> {
+    fn expr_break(&mut self, span: Span, attrs: AttrVec) -> P<hir::Expr> {
         let expr_break = hir::ExprKind::Break(self.lower_loop_destination(None), None);
         P(self.expr(span, expr_break, attrs))
     }
@@ -1404,7 +1403,7 @@ impl LoweringContext<'_> {
         span: Span,
         components: &[Symbol],
         params: Option<P<hir::GenericArgs>>,
-        attrs: ThinVec<Attribute>,
+        attrs: AttrVec,
     ) -> hir::Expr {
         let path = self.std_path(span, components, params, true);
         self.expr(
@@ -1423,7 +1422,7 @@ impl LoweringContext<'_> {
         span: Span,
         ident: Ident,
         binding: hir::HirId,
-        attrs: ThinVec<Attribute>,
+        attrs: AttrVec,
     ) -> hir::Expr {
         let expr_path = hir::ExprKind::Path(hir::QPath::Resolved(
             None,
@@ -1459,16 +1458,11 @@ impl LoweringContext<'_> {
         self.expr_block(P(blk), ThinVec::new())
     }
 
-    pub(super) fn expr_block(&mut self, b: P<hir::Block>, attrs: ThinVec<Attribute>) -> hir::Expr {
+    pub(super) fn expr_block(&mut self, b: P<hir::Block>, attrs: AttrVec) -> hir::Expr {
         self.expr(b.span, hir::ExprKind::Block(b, None), attrs)
     }
 
-    pub(super) fn expr(
-        &mut self,
-        span: Span,
-        kind: hir::ExprKind,
-        attrs: ThinVec<Attribute>
-    ) -> hir::Expr {
+    pub(super) fn expr(&mut self, span: Span, kind: hir::ExprKind, attrs: AttrVec) -> hir::Expr {
         hir::Expr { hir_id: self.next_id(), kind, span, attrs }
     }
 
diff --git a/src/librustc/hir/lowering/item.rs b/src/librustc/hir/lowering/item.rs
index 46c944fa678..6cae8e2cc04 100644
--- a/src/librustc/hir/lowering/item.rs
+++ b/src/librustc/hir/lowering/item.rs
@@ -11,7 +11,6 @@ use crate::hir::def_id::DefId;
 use crate::hir::def::{Res, DefKind};
 use crate::util::nodemap::NodeMap;
 
-use rustc_data_structures::thin_vec::ThinVec;
 use rustc_target::spec::abi;
 
 use std::collections::BTreeSet;
@@ -899,7 +898,7 @@ impl LoweringContext<'_> {
 
     /// Construct `ExprKind::Err` for the given `span`.
     fn expr_err(&mut self, span: Span) -> hir::Expr {
-        self.expr(span, hir::ExprKind::Err, ThinVec::new())
+        self.expr(span, hir::ExprKind::Err, AttrVec::new())
     }
 
     fn lower_impl_item(&mut self, i: &AssocItem) -> hir::ImplItem {
@@ -1182,7 +1181,7 @@ impl LoweringContext<'_> {
                 //
                 // If this is the simple case, this parameter will end up being the same as the
                 // original parameter, but with a different pattern id.
-                let mut stmt_attrs = ThinVec::new();
+                let mut stmt_attrs = AttrVec::new();
                 stmt_attrs.extend(parameter.attrs.iter().cloned());
                 let (new_parameter_pat, new_parameter_id) = this.pat_ident(desugared_span, ident);
                 let new_parameter = hir::Param {
@@ -1226,7 +1225,7 @@ impl LoweringContext<'_> {
                         desugared_span, ident, hir::BindingAnnotation::Mutable);
                     let move_expr = this.expr_ident(desugared_span, ident, new_parameter_id);
                     let move_stmt = this.stmt_let_pat(
-                        ThinVec::new(),
+                        AttrVec::new(),
                         desugared_span,
                         Some(P(move_expr)),
                         move_pat,
@@ -1271,7 +1270,7 @@ impl LoweringContext<'_> {
                     let user_body = this.expr_drop_temps(
                         desugared_span,
                         P(user_body),
-                        ThinVec::new(),
+                        AttrVec::new(),
                     );
 
                     // As noted above, create the final block like
@@ -1288,9 +1287,9 @@ impl LoweringContext<'_> {
                         statements.into(),
                         Some(P(user_body)),
                     );
-                    this.expr_block(P(body), ThinVec::new())
+                    this.expr_block(P(body), AttrVec::new())
                 });
-            (HirVec::from(parameters), this.expr(body_span, async_expr, ThinVec::new()))
+            (HirVec::from(parameters), this.expr(body_span, async_expr, AttrVec::new()))
         })
     }
 
diff --git a/src/librustc/hir/mod.rs b/src/librustc/hir/mod.rs
index 2cffcc5bfad..368269ff200 100644
--- a/src/librustc/hir/mod.rs
+++ b/src/librustc/hir/mod.rs
@@ -20,7 +20,7 @@ use errors::FatalError;
 use syntax_pos::{Span, DUMMY_SP, MultiSpan};
 use syntax::source_map::Spanned;
 use syntax::ast::{self, CrateSugar, Ident, Name, NodeId, AsmDialect};
-use syntax::ast::{Attribute, Label, LitKind, StrStyle, FloatTy, IntTy, UintTy};
+use syntax::ast::{AttrVec, Attribute, Label, LitKind, StrStyle, FloatTy, IntTy, UintTy};
 pub use syntax::ast::{Mutability, Constness, Unsafety, Movability, CaptureBy};
 pub use syntax::ast::{IsAuto, ImplPolarity, BorrowKind};
 use syntax::attr::{InlineAttr, OptimizeAttr};
@@ -29,7 +29,6 @@ use syntax::tokenstream::TokenStream;
 use syntax::util::parser::ExprPrecedence;
 use rustc_target::spec::abi::Abi;
 use rustc_data_structures::sync::{par_for_each_in, Send, Sync};
-use rustc_data_structures::thin_vec::ThinVec;
 use rustc_macros::HashStable;
 use rustc_serialize::{self, Encoder, Encodable, Decoder, Decodable};
 use std::collections::{BTreeSet, BTreeMap};
@@ -1274,7 +1273,7 @@ pub struct Local {
     pub init: Option<P<Expr>>,
     pub hir_id: HirId,
     pub span: Span,
-    pub attrs: ThinVec<Attribute>,
+    pub attrs: AttrVec,
     /// Can be `ForLoopDesugar` if the `let` statement is part of a `for` loop
     /// desugaring. Otherwise will be `Normal`.
     pub source: LocalSource,
@@ -1459,7 +1458,7 @@ pub struct AnonConst {
 pub struct Expr {
     pub hir_id: HirId,
     pub kind: ExprKind,
-    pub attrs: ThinVec<Attribute>,
+    pub attrs: AttrVec,
     pub span: Span,
 }
 
diff --git a/src/librustc_interface/util.rs b/src/librustc_interface/util.rs
index 4d686fc310f..a8800082c9a 100644
--- a/src/librustc_interface/util.rs
+++ b/src/librustc_interface/util.rs
@@ -10,7 +10,6 @@ use rustc_data_structures::jobserver;
 use rustc_data_structures::sync::{Lock, Lrc};
 use rustc_data_structures::stable_hasher::StableHasher;
 use rustc_data_structures::fingerprint::Fingerprint;
-use rustc_data_structures::thin_vec::ThinVec;
 use rustc_data_structures::fx::{FxHashSet, FxHashMap};
 use rustc_errors::registry::Registry;
 use rustc_metadata::dynamic_lib::DynamicLibrary;
@@ -24,7 +23,7 @@ use std::ops::DerefMut;
 use smallvec::SmallVec;
 use syntax::ptr::P;
 use syntax::mut_visit::{*, MutVisitor, visit_clobber};
-use syntax::ast::BlockCheckMode;
+use syntax::ast::{AttrVec, BlockCheckMode};
 use syntax::util::lev_distance::find_best_match_for_name;
 use syntax::source_map::{FileLoader, RealFileLoader, SourceMap};
 use syntax::symbol::{Symbol, sym};
@@ -741,7 +740,7 @@ impl<'a> MutVisitor for ReplaceBodyWithLoop<'a, '_> {
                 id: resolver.next_node_id(),
                 kind: ast::ExprKind::Block(P(b), None),
                 span: syntax_pos::DUMMY_SP,
-                attrs: ThinVec::new(),
+                attrs: AttrVec::new(),
             });
 
             ast::Stmt {
@@ -756,7 +755,7 @@ impl<'a> MutVisitor for ReplaceBodyWithLoop<'a, '_> {
             kind: ast::ExprKind::Loop(P(empty_block), None),
             id: self.resolver.next_node_id(),
             span: syntax_pos::DUMMY_SP,
-                attrs: ThinVec::new(),
+                attrs: AttrVec::new(),
         });
 
         let loop_stmt = ast::Stmt {
diff --git a/src/librustc_parse/lib.rs b/src/librustc_parse/lib.rs
index 58c36524380..3de7f888724 100644
--- a/src/librustc_parse/lib.rs
+++ b/src/librustc_parse/lib.rs
@@ -2,6 +2,7 @@
 
 #![feature(bool_to_option)]
 #![feature(crate_visibility_modifier)]
+#![feature(slice_patterns)]
 
 use syntax::ast;
 use syntax::print::pprust;
diff --git a/src/librustc_parse/parser/attr.rs b/src/librustc_parse/parser/attr.rs
index 00fd6b8a25b..51310fb88f6 100644
--- a/src/librustc_parse/parser/attr.rs
+++ b/src/librustc_parse/parser/attr.rs
@@ -1,4 +1,4 @@
-use super::{SeqSep, Parser, TokenType, PathStyle};
+use super::{Parser, TokenType, PathStyle};
 use rustc_errors::PResult;
 use syntax::attr;
 use syntax::ast;
@@ -301,8 +301,10 @@ impl<'a> Parser<'a> {
     crate fn parse_meta_item_kind(&mut self) -> PResult<'a, ast::MetaItemKind> {
         Ok(if self.eat(&token::Eq) {
             ast::MetaItemKind::NameValue(self.parse_unsuffixed_lit()?)
-        } else if self.eat(&token::OpenDelim(token::Paren)) {
-            ast::MetaItemKind::List(self.parse_meta_seq()?)
+        } else if self.check(&token::OpenDelim(token::Paren)) {
+            // Matches `meta_seq = ( COMMASEP(meta_item_inner) )`.
+            let (list, _) = self.parse_paren_comma_seq(|p| p.parse_meta_item_inner())?;
+            ast::MetaItemKind::List(list)
         } else {
             ast::MetaItemKind::Word
         })
@@ -311,16 +313,12 @@ impl<'a> Parser<'a> {
     /// Matches `meta_item_inner : (meta_item | UNSUFFIXED_LIT) ;`.
     fn parse_meta_item_inner(&mut self) -> PResult<'a, ast::NestedMetaItem> {
         match self.parse_unsuffixed_lit() {
-            Ok(lit) => {
-                return Ok(ast::NestedMetaItem::Literal(lit))
-            }
+            Ok(lit) => return Ok(ast::NestedMetaItem::Literal(lit)),
             Err(ref mut err) => err.cancel(),
         }
 
         match self.parse_meta_item() {
-            Ok(mi) => {
-                return Ok(ast::NestedMetaItem::MetaItem(mi))
-            }
+            Ok(mi) => return Ok(ast::NestedMetaItem::MetaItem(mi)),
             Err(ref mut err) => err.cancel(),
         }
 
@@ -328,11 +326,4 @@ impl<'a> Parser<'a> {
         let msg = format!("expected unsuffixed literal or identifier, found `{}`", found);
         Err(self.diagnostic().struct_span_err(self.token.span, &msg))
     }
-
-    /// Matches `meta_seq = ( COMMASEP(meta_item_inner) )`.
-    fn parse_meta_seq(&mut self) -> PResult<'a, Vec<ast::NestedMetaItem>> {
-        self.parse_seq_to_end(&token::CloseDelim(token::Paren),
-                              SeqSep::trailing_allowed(token::Comma),
-                              |p: &mut Parser<'a>| p.parse_meta_item_inner())
-    }
 }
diff --git a/src/librustc_parse/parser/diagnostics.rs b/src/librustc_parse/parser/diagnostics.rs
index ba125cacab4..16daefd1450 100644
--- a/src/librustc_parse/parser/diagnostics.rs
+++ b/src/librustc_parse/parser/diagnostics.rs
@@ -4,14 +4,13 @@ use rustc_data_structures::fx::FxHashSet;
 use rustc_errors::{self, PResult, Applicability, DiagnosticBuilder, Handler, pluralize};
 use rustc_error_codes::*;
 use syntax::ast::{self, Param, BinOpKind, BindingMode, BlockCheckMode, Expr, ExprKind, Ident, Item};
-use syntax::ast::{ItemKind, Mutability, Pat, PatKind, PathSegment, QSelf, Ty, TyKind};
+use syntax::ast::{ItemKind, Mutability, Pat, PatKind, PathSegment, QSelf, Ty, TyKind, AttrVec};
 use syntax::token::{self, TokenKind, token_can_begin_expr};
 use syntax::print::pprust;
 use syntax::ptr::P;
-use syntax::ThinVec;
 use syntax::util::parser::AssocOp;
 use syntax::struct_span_err;
-use syntax_pos::symbol::{kw, sym};
+use syntax_pos::symbol::kw;
 use syntax_pos::{Span, DUMMY_SP, MultiSpan, SpanSnippetError};
 
 use log::{debug, trace};
@@ -32,7 +31,7 @@ pub(super) fn dummy_arg(ident: Ident) -> Param {
         id: ast::DUMMY_NODE_ID
     };
     Param {
-        attrs: ThinVec::default(),
+        attrs: AttrVec::default(),
         id: ast::DUMMY_NODE_ID,
         pat,
         span: ident.span,
@@ -164,7 +163,7 @@ impl RecoverQPath for Expr {
         Self {
             span: path.span,
             kind: ExprKind::Path(qself, path),
-            attrs: ThinVec::new(),
+            attrs: AttrVec::new(),
             id: ast::DUMMY_NODE_ID,
         }
     }
@@ -312,22 +311,6 @@ impl<'a> Parser<'a> {
         };
         self.last_unexpected_token_span = Some(self.token.span);
         let mut err = self.fatal(&msg_exp);
-        if self.token.is_ident_named(sym::and) {
-            err.span_suggestion_short(
-                self.token.span,
-                "use `&&` instead of `and` for the boolean operator",
-                "&&".to_string(),
-                Applicability::MaybeIncorrect,
-            );
-        }
-        if self.token.is_ident_named(sym::or) {
-            err.span_suggestion_short(
-                self.token.span,
-                "use `||` instead of `or` for the boolean operator",
-                "||".to_string(),
-                Applicability::MaybeIncorrect,
-            );
-        }
         let sp = if self.token == token::Eof {
             // This is EOF; don't want to point at the following char, but rather the last token.
             self.prev_span
@@ -567,7 +550,7 @@ impl<'a> Parser<'a> {
         );
 
         let mk_err_expr = |this: &Self, span| {
-            Ok(Some(this.mk_expr(span, ExprKind::Err, ThinVec::new())))
+            Ok(Some(this.mk_expr(span, ExprKind::Err, AttrVec::new())))
         };
 
         match lhs.kind {
@@ -986,21 +969,32 @@ impl<'a> Parser<'a> {
 
     /// Consumes alternative await syntaxes like `await!(<expr>)`, `await <expr>`,
     /// `await? <expr>`, `await(<expr>)`, and `await { <expr> }`.
-    pub(super) fn parse_incorrect_await_syntax(
+    pub(super) fn recover_incorrect_await_syntax(
         &mut self,
         lo: Span,
         await_sp: Span,
-    ) -> PResult<'a, (Span, ExprKind)> {
-        if self.token == token::Not {
+        attrs: AttrVec,
+    ) -> PResult<'a, P<Expr>> {
+        let (hi, expr, is_question) = if self.token == token::Not {
             // Handle `await!(<expr>)`.
-            self.expect(&token::Not)?;
-            self.expect(&token::OpenDelim(token::Paren))?;
-            let expr = self.parse_expr()?;
-            self.expect(&token::CloseDelim(token::Paren))?;
-            let sp = self.error_on_incorrect_await(lo, self.prev_span, &expr, false);
-            return Ok((sp, ExprKind::Await(expr)))
-        }
+            self.recover_await_macro()?
+        } else {
+            self.recover_await_prefix(await_sp)?
+        };
+        let sp = self.error_on_incorrect_await(lo, hi, &expr, is_question);
+        let expr = self.mk_expr(lo.to(sp), ExprKind::Await(expr), attrs);
+        self.maybe_recover_from_bad_qpath(expr, true)
+    }
+
+    fn recover_await_macro(&mut self) -> PResult<'a, (Span, P<Expr>, bool)> {
+        self.expect(&token::Not)?;
+        self.expect(&token::OpenDelim(token::Paren))?;
+        let expr = self.parse_expr()?;
+        self.expect(&token::CloseDelim(token::Paren))?;
+        Ok((self.prev_span, expr, false))
+    }
 
+    fn recover_await_prefix(&mut self, await_sp: Span) -> PResult<'a, (Span, P<Expr>, bool)> {
         let is_question = self.eat(&token::Question); // Handle `await? <expr>`.
         let expr = if self.token == token::OpenDelim(token::Brace) {
             // Handle `await { <expr> }`.
@@ -1010,7 +1004,7 @@ impl<'a> Parser<'a> {
                 None,
                 self.token.span,
                 BlockCheckMode::Default,
-                ThinVec::new(),
+                AttrVec::new(),
             )
         } else {
             self.parse_expr()
@@ -1018,8 +1012,7 @@ impl<'a> Parser<'a> {
             err.span_label(await_sp, "while parsing this incorrect await expression");
             err
         })?;
-        let sp = self.error_on_incorrect_await(lo, expr.span, &expr, is_question);
-        Ok((sp, ExprKind::Await(expr)))
+        Ok((expr.span, expr, is_question))
     }
 
     fn error_on_incorrect_await(&self, lo: Span, hi: Span, expr: &Expr, is_question: bool) -> Span {
@@ -1132,7 +1125,7 @@ impl<'a> Parser<'a> {
                 err.emit();
                 // Recover from parse error, callers expect the closing delim to be consumed.
                 self.consume_block(delim, ConsumeClosingDelim::Yes);
-                self.mk_expr(lo.to(self.prev_span), ExprKind::Err, ThinVec::new())
+                self.mk_expr(lo.to(self.prev_span), ExprKind::Err, AttrVec::new())
             }
         }
     }
diff --git a/src/librustc_parse/parser/expr.rs b/src/librustc_parse/parser/expr.rs
index e4dff07e92c..159c2121d36 100644
--- a/src/librustc_parse/parser/expr.rs
+++ b/src/librustc_parse/parser/expr.rs
@@ -4,11 +4,10 @@ use super::pat::{GateOr, PARAM_EXPECTED};
 use super::diagnostics::Error;
 use crate::maybe_recover_from_interpolated_ty_qpath;
 
-use rustc_data_structures::thin_vec::ThinVec;
 use rustc_errors::{PResult, Applicability};
-use syntax::ast::{self, DUMMY_NODE_ID, Attribute, AttrStyle, Ident, CaptureBy, BlockCheckMode};
-use syntax::ast::{Expr, ExprKind, RangeLimits, Label, Movability, IsAsync, Arm, Ty, TyKind};
-use syntax::ast::{FunctionRetTy, Param, FnDecl, BinOpKind, BinOp, UnOp, Mac, AnonConst, Field, Lit};
+use syntax::ast::{self, DUMMY_NODE_ID, AttrVec, AttrStyle, Ident, CaptureBy, Field, Lit};
+use syntax::ast::{BlockCheckMode, Expr, ExprKind, RangeLimits, Label, Movability, IsAsync, Arm};
+use syntax::ast::{Ty, TyKind, FunctionRetTy, Param, FnDecl, BinOpKind, BinOp, UnOp, Mac, AnonConst};
 use syntax::token::{self, Token, TokenKind};
 use syntax::print::pprust;
 use syntax::ptr::P;
@@ -37,14 +36,14 @@ macro_rules! maybe_whole_expr {
                     let path = path.clone();
                     $p.bump();
                     return Ok($p.mk_expr(
-                        $p.token.span, ExprKind::Path(None, path), ThinVec::new()
+                        $p.token.span, ExprKind::Path(None, path), AttrVec::new()
                     ));
                 }
                 token::NtBlock(block) => {
                     let block = block.clone();
                     $p.bump();
                     return Ok($p.mk_expr(
-                        $p.token.span, ExprKind::Block(block, None), ThinVec::new()
+                        $p.token.span, ExprKind::Block(block, None), AttrVec::new()
                     ));
                 }
                 // N.B., `NtIdent(ident)` is normalized to `Ident` in `fn bump`.
@@ -57,16 +56,16 @@ macro_rules! maybe_whole_expr {
 #[derive(Debug)]
 pub(super) enum LhsExpr {
     NotYetParsed,
-    AttributesParsed(ThinVec<Attribute>),
+    AttributesParsed(AttrVec),
     AlreadyParsed(P<Expr>),
 }
 
-impl From<Option<ThinVec<Attribute>>> for LhsExpr {
+impl From<Option<AttrVec>> for LhsExpr {
     /// Converts `Some(attrs)` into `LhsExpr::AttributesParsed(attrs)`
     /// and `None` into `LhsExpr::NotYetParsed`.
     ///
     /// This conversion does not allocate.
-    fn from(o: Option<ThinVec<Attribute>>) -> Self {
+    fn from(o: Option<AttrVec>) -> Self {
         if let Some(attrs) = o {
             LhsExpr::AttributesParsed(attrs)
         } else {
@@ -91,24 +90,29 @@ impl<'a> Parser<'a> {
         self.parse_expr_res(Restrictions::empty(), None)
     }
 
+    fn parse_expr_catch_underscore(&mut self) -> PResult<'a, P<Expr>> {
+        match self.parse_expr() {
+            Ok(expr) => Ok(expr),
+            Err(mut err) => match self.token.kind {
+                token::Ident(name, false)
+                if name == kw::Underscore && self.look_ahead(1, |t| {
+                    t == &token::Comma
+                }) => {
+                    // Special-case handling of `foo(_, _, _)`
+                    err.emit();
+                    let sp = self.token.span;
+                    self.bump();
+                    Ok(self.mk_expr(sp, ExprKind::Err, AttrVec::new()))
+                }
+                _ => Err(err),
+            },
+        }
+    }
+
+    /// Parses a sequence of expressions bounded by parentheses.
     fn parse_paren_expr_seq(&mut self) -> PResult<'a, Vec<P<Expr>>> {
         self.parse_paren_comma_seq(|p| {
-            match p.parse_expr() {
-                Ok(expr) => Ok(expr),
-                Err(mut err) => match p.token.kind {
-                    token::Ident(name, false)
-                    if name == kw::Underscore && p.look_ahead(1, |t| {
-                        t == &token::Comma
-                    }) => {
-                        // Special-case handling of `foo(_, _, _)`
-                        err.emit();
-                        let sp = p.token.span;
-                        p.bump();
-                        Ok(p.mk_expr(sp, ExprKind::Err, ThinVec::new()))
-                    }
-                    _ => Err(err),
-                },
-            }
+            p.parse_expr_catch_underscore()
         }).map(|(r, _)| r)
     }
 
@@ -117,7 +121,7 @@ impl<'a> Parser<'a> {
     pub(super) fn parse_expr_res(
         &mut self,
         r: Restrictions,
-        already_parsed_attrs: Option<ThinVec<Attribute>>
+        already_parsed_attrs: Option<AttrVec>
     ) -> PResult<'a, P<Expr>> {
         self.with_res(r, |this| this.parse_assoc_expr(already_parsed_attrs))
     }
@@ -129,7 +133,7 @@ impl<'a> Parser<'a> {
     #[inline]
     fn parse_assoc_expr(
         &mut self,
-        already_parsed_attrs: Option<ThinVec<Attribute>>,
+        already_parsed_attrs: Option<AttrVec>,
     ) -> PResult<'a, P<Expr>> {
         self.parse_assoc_expr_with(0, already_parsed_attrs.into())
     }
@@ -155,53 +159,13 @@ impl<'a> Parser<'a> {
         };
         let last_type_ascription_set = self.last_type_ascription.is_some();
 
-        match (self.expr_is_complete(&lhs), AssocOp::from_token(&self.token)) {
-            (true, None) => {
-                self.last_type_ascription = None;
-                // Semi-statement forms are odd. See https://github.com/rust-lang/rust/issues/29071
-                return Ok(lhs);
-            }
-            (false, _) => {} // continue parsing the expression
-            // An exhaustive check is done in the following block, but these are checked first
-            // because they *are* ambiguous but also reasonable looking incorrect syntax, so we
-            // want to keep their span info to improve diagnostics in these cases in a later stage.
-            (true, Some(AssocOp::Multiply)) | // `{ 42 } *foo = bar;` or `{ 42 } * 3`
-            (true, Some(AssocOp::Subtract)) | // `{ 42 } -5`
-            (true, Some(AssocOp::LAnd)) | // `{ 42 } &&x` (#61475)
-            (true, Some(AssocOp::Add)) // `{ 42 } + 42
-            // If the next token is a keyword, then the tokens above *are* unambiguously incorrect:
-            // `if x { a } else { b } && if y { c } else { d }`
-            if !self.look_ahead(1, |t| t.is_reserved_ident()) => {
-                self.last_type_ascription = None;
-                // These cases are ambiguous and can't be identified in the parser alone
-                let sp = self.sess.source_map().start_point(self.token.span);
-                self.sess.ambiguous_block_expr_parse.borrow_mut().insert(sp, lhs.span);
-                return Ok(lhs);
-            }
-            (true, Some(ref op)) if !op.can_continue_expr_unambiguously() => {
-                self.last_type_ascription = None;
-                return Ok(lhs);
-            }
-            (true, Some(_)) => {
-                // We've found an expression that would be parsed as a statement, but the next
-                // token implies this should be parsed as an expression.
-                // For example: `if let Some(x) = x { x } else { 0 } / 2`
-                let mut err = self.struct_span_err(self.token.span, &format!(
-                    "expected expression, found `{}`",
-                    pprust::token_to_string(&self.token),
-                ));
-                err.span_label(self.token.span, "expected expression");
-                self.sess.expr_parentheses_needed(
-                    &mut err,
-                    lhs.span,
-                    Some(pprust::expr_to_string(&lhs),
-                ));
-                err.emit();
-            }
+        if !self.should_continue_as_assoc_expr(&lhs) {
+            self.last_type_ascription = None;
+            return Ok(lhs);
         }
-        self.expected_tokens.push(TokenType::Operator);
-        while let Some(op) = AssocOp::from_token(&self.token) {
 
+        self.expected_tokens.push(TokenType::Operator);
+        while let Some(op) = self.check_assoc_op() {
             // Adjust the span for interpolated LHS to point to the `$lhs` token and not to what
             // it refers to. Interpolated identifiers are unwrapped early and never show up here
             // as `PrevTokenKind::Interpolated` so if LHS is a single identifier we always process
@@ -272,7 +236,7 @@ impl<'a> Parser<'a> {
                 };
 
                 let r = self.mk_range(Some(lhs), rhs, limits)?;
-                lhs = self.mk_expr(lhs_span.to(rhs_span), r, ThinVec::new());
+                lhs = self.mk_expr(lhs_span.to(rhs_span), r, AttrVec::new());
                 break
             }
 
@@ -306,9 +270,9 @@ impl<'a> Parser<'a> {
                 AssocOp::Greater | AssocOp::GreaterEqual => {
                     let ast_op = op.to_ast_binop().unwrap();
                     let binary = self.mk_binary(source_map::respan(cur_op_span, ast_op), lhs, rhs);
-                    self.mk_expr(span, binary, ThinVec::new())
+                    self.mk_expr(span, binary, AttrVec::new())
                 }
-                AssocOp::Assign => self.mk_expr(span, ExprKind::Assign(lhs, rhs), ThinVec::new()),
+                AssocOp::Assign => self.mk_expr(span, ExprKind::Assign(lhs, rhs), AttrVec::new()),
                 AssocOp::AssignOp(k) => {
                     let aop = match k {
                         token::Plus =>    BinOpKind::Add,
@@ -323,7 +287,7 @@ impl<'a> Parser<'a> {
                         token::Shr =>     BinOpKind::Shr,
                     };
                     let aopexpr = self.mk_assign_op(source_map::respan(cur_op_span, aop), lhs, rhs);
-                    self.mk_expr(span, aopexpr, ThinVec::new())
+                    self.mk_expr(span, aopexpr, AttrVec::new())
                 }
                 AssocOp::As | AssocOp::Colon | AssocOp::DotDot | AssocOp::DotDotEq => {
                     self.bug("AssocOp should have been handled by special case")
@@ -338,6 +302,80 @@ impl<'a> Parser<'a> {
         Ok(lhs)
     }
 
+    fn should_continue_as_assoc_expr(&mut self, lhs: &Expr) -> bool {
+        match (self.expr_is_complete(lhs), self.check_assoc_op()) {
+            // Semi-statement forms are odd:
+            // See https://github.com/rust-lang/rust/issues/29071
+            (true, None) => false,
+            (false, _) => true, // Continue parsing the expression.
+            // An exhaustive check is done in the following block, but these are checked first
+            // because they *are* ambiguous but also reasonable looking incorrect syntax, so we
+            // want to keep their span info to improve diagnostics in these cases in a later stage.
+            (true, Some(AssocOp::Multiply)) | // `{ 42 } *foo = bar;` or `{ 42 } * 3`
+            (true, Some(AssocOp::Subtract)) | // `{ 42 } -5`
+            (true, Some(AssocOp::LAnd)) | // `{ 42 } &&x` (#61475)
+            (true, Some(AssocOp::Add)) // `{ 42 } + 42
+            // If the next token is a keyword, then the tokens above *are* unambiguously incorrect:
+            // `if x { a } else { b } && if y { c } else { d }`
+            if !self.look_ahead(1, |t| t.is_reserved_ident()) => {
+                // These cases are ambiguous and can't be identified in the parser alone.
+                let sp = self.sess.source_map().start_point(self.token.span);
+                self.sess.ambiguous_block_expr_parse.borrow_mut().insert(sp, lhs.span);
+                false
+            }
+            (true, Some(ref op)) if !op.can_continue_expr_unambiguously() => false,
+            (true, Some(_)) => {
+                self.error_found_expr_would_be_stmt(lhs);
+                true
+            }
+        }
+    }
+
+    /// We've found an expression that would be parsed as a statement,
+    /// but the next token implies this should be parsed as an expression.
+    /// For example: `if let Some(x) = x { x } else { 0 } / 2`.
+    fn error_found_expr_would_be_stmt(&self, lhs: &Expr) {
+        let mut err = self.struct_span_err(self.token.span, &format!(
+            "expected expression, found `{}`",
+            pprust::token_to_string(&self.token),
+        ));
+        err.span_label(self.token.span, "expected expression");
+        self.sess.expr_parentheses_needed(&mut err, lhs.span, Some(pprust::expr_to_string(&lhs)));
+        err.emit();
+    }
+
+    /// Possibly translate the current token to an associative operator.
+    /// 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<AssocOp> {
+        match (AssocOp::from_token(&self.token), &self.token.kind) {
+            (op @ Some(_), _) => op,
+            (None, token::Ident(sym::and, false)) => {
+                self.error_bad_logical_op("and", "&&", "conjunction");
+                Some(AssocOp::LAnd)
+            }
+            (None, token::Ident(sym::or, false)) => {
+                self.error_bad_logical_op("or", "||", "disjunction");
+                Some(AssocOp::LOr)
+            }
+            _ => None,
+        }
+    }
+
+    /// Error on `and` and `or` suggesting `&&` and `||` respectively.
+    fn error_bad_logical_op(&self, bad: &str, good: &str, english: &str) {
+        self.struct_span_err(self.token.span, &format!("`{}` is not a logical operator", bad))
+            .span_suggestion_short(
+                self.token.span,
+                &format!("use `{}` to perform logical {}", good, english),
+                good.to_string(),
+                Applicability::MachineApplicable,
+            )
+            .note("unlike in e.g., python and PHP, `&&` and `||` are used for logical operators")
+            .emit();
+    }
+
     /// Checks if this expression is a successfully parsed statement.
     fn expr_is_complete(&self, e: &Expr) -> bool {
         self.restrictions.contains(Restrictions::STMT_EXPR) &&
@@ -359,7 +397,7 @@ impl<'a> Parser<'a> {
     /// Parses prefix-forms of range notation: `..expr`, `..`, `..=expr`.
     fn parse_prefix_range_expr(
         &mut self,
-        already_parsed_attrs: Option<ThinVec<Attribute>>
+        already_parsed_attrs: Option<AttrVec>
     ) -> PResult<'a, P<Expr>> {
         // Check for deprecated `...` syntax.
         if self.token == token::DotDotDot {
@@ -396,10 +434,7 @@ impl<'a> Parser<'a> {
     }
 
     /// Parses a prefix-unary-operator expr.
-    fn parse_prefix_expr(
-        &mut self,
-        already_parsed_attrs: Option<ThinVec<Attribute>>
-    ) -> PResult<'a, P<Expr>> {
+    fn parse_prefix_expr(&mut self, already_parsed_attrs: Option<AttrVec>) -> PResult<'a, P<Expr>> {
         let attrs = self.parse_or_use_outer_attributes(already_parsed_attrs)?;
         let lo = self.token.span;
         // Note: when adding new unary operators, don't forget to adjust TokenKind::can_begin_expr()
@@ -510,7 +545,7 @@ impl<'a> Parser<'a> {
                            expr_kind: fn(P<Expr>, P<Ty>) -> ExprKind)
                            -> PResult<'a, P<Expr>> {
         let mk_expr = |this: &mut Self, rhs: P<Ty>| {
-            this.mk_expr(lhs_span.to(rhs.span), expr_kind(lhs, rhs), ThinVec::new())
+            this.mk_expr(lhs_span.to(rhs.span), expr_kind(lhs, rhs), AttrVec::new())
         };
 
         // Save the state of the parser before parsing type normally, in case there is a
@@ -611,7 +646,7 @@ impl<'a> Parser<'a> {
     /// Parses `a.b` or `a(13)` or `a[4]` or just `a`.
     fn parse_dot_or_call_expr(
         &mut self,
-        already_parsed_attrs: Option<ThinVec<Attribute>>,
+        already_parsed_attrs: Option<AttrVec>,
     ) -> PResult<'a, P<Expr>> {
         let attrs = self.parse_or_use_outer_attributes(already_parsed_attrs)?;
 
@@ -624,7 +659,7 @@ impl<'a> Parser<'a> {
         &mut self,
         e0: P<Expr>,
         lo: Span,
-        mut attrs: ThinVec<Attribute>,
+        mut attrs: AttrVec,
     ) -> PResult<'a, P<Expr>> {
         // Stitch the list of outer attributes onto the return value.
         // A little bit ugly, but the best way given the current code
@@ -633,19 +668,20 @@ impl<'a> Parser<'a> {
             expr.map(|mut expr| {
                 attrs.extend::<Vec<_>>(expr.attrs.into());
                 expr.attrs = attrs;
-                match expr.kind {
-                    ExprKind::If(..) if !expr.attrs.is_empty() => {
-                        // Just point to the first attribute in there...
-                        let span = expr.attrs[0].span;
-                        self.span_err(span, "attributes are not yet allowed on `if` expressions");
-                    }
-                    _ => {}
-                }
+                self.error_attr_on_if_expr(&expr);
                 expr
             })
         )
     }
 
+    fn error_attr_on_if_expr(&self, expr: &Expr) {
+        if let (ExprKind::If(..), [a0, ..]) = (&expr.kind, &*expr.attrs) {
+            // Just point to the first attribute in there...
+            self.struct_span_err(a0.span, "attributes are not yet allowed on `if` expressions")
+                .emit();
+        }
+    }
+
     fn parse_dot_or_call_expr_with_(&mut self, e0: P<Expr>, lo: Span) -> PResult<'a, P<Expr>> {
         let mut e = e0;
         let mut hi;
@@ -653,7 +689,7 @@ impl<'a> Parser<'a> {
             // expr?
             while self.eat(&token::Question) {
                 let hi = self.prev_span;
-                e = self.mk_expr(lo.to(hi), ExprKind::Try(e), ThinVec::new());
+                e = self.mk_expr(lo.to(hi), ExprKind::Try(e), AttrVec::new());
             }
 
             // expr.f
@@ -666,7 +702,7 @@ impl<'a> Parser<'a> {
                         let span = self.token.span;
                         self.bump();
                         let field = ExprKind::Field(e, Ident::new(symbol, span));
-                        e = self.mk_expr(lo.to(span), field, ThinVec::new());
+                        e = self.mk_expr(lo.to(span), field, AttrVec::new());
 
                         self.expect_no_suffix(span, "a tuple index", suffix);
                     }
@@ -715,7 +751,7 @@ impl<'a> Parser<'a> {
                     let seq = self.parse_paren_expr_seq().map(|es| {
                         let nd = self.mk_call(e, es);
                         let hi = self.prev_span;
-                        self.mk_expr(lo.to(hi), nd, ThinVec::new())
+                        self.mk_expr(lo.to(hi), nd, AttrVec::new())
                     });
                     e = self.recover_seq_parse_error(token::Paren, lo, seq);
                 }
@@ -728,7 +764,7 @@ impl<'a> Parser<'a> {
                     hi = self.token.span;
                     self.expect(&token::CloseDelim(token::Bracket))?;
                     let index = self.mk_index(e, ix);
-                    e = self.mk_expr(lo.to(hi), index, ThinVec::new())
+                    e = self.mk_expr(lo.to(hi), index, AttrVec::new())
                 }
                 _ => return Ok(e)
             }
@@ -752,7 +788,7 @@ impl<'a> Parser<'a> {
                 args.insert(0, self_arg);
 
                 let span = lo.to(self.prev_span);
-                self.mk_expr(span, ExprKind::MethodCall(segment, args), ThinVec::new())
+                self.mk_expr(span, ExprKind::MethodCall(segment, args), AttrVec::new())
             }
             _ => {
                 // Field access `expr.f`
@@ -762,7 +798,7 @@ impl<'a> Parser<'a> {
                 }
 
                 let span = lo.to(self.prev_span);
-                self.mk_expr(span, ExprKind::Field(self_arg, segment.ident), ThinVec::new())
+                self.mk_expr(span, ExprKind::Field(self_arg, segment.ident), AttrVec::new())
             }
         })
     }
@@ -781,305 +817,278 @@ impl<'a> Parser<'a> {
         //
         // Therefore, prevent sub-parser from parsing
         // attributes by giving them a empty "already-parsed" list.
-        let mut attrs = ThinVec::new();
+        let attrs = AttrVec::new();
 
+        // Note: when adding new syntax here, don't forget to adjust `TokenKind::can_begin_expr()`.
         let lo = self.token.span;
-        let mut hi = self.token.span;
-
-        let ex: ExprKind;
-
-        macro_rules! parse_lit {
-            () => {
-                match self.parse_opt_lit() {
-                    Some(literal) => {
-                        hi = self.prev_span;
-                        ex = ExprKind::Lit(literal);
-                    }
-                    None => {
-                        return Err(self.expected_expression_found());
-                    }
+        if let token::Literal(_) = self.token.kind {
+            // This match arm is a special-case of the `_` match arm below and
+            // could be removed without changing functionality, but it's faster
+            // to have it here, especially for programs with large constants.
+            self.parse_lit_expr(attrs)
+        } else if self.check(&token::OpenDelim(token::Paren)) {
+            self.parse_tuple_parens_expr(attrs)
+        } else if self.check(&token::OpenDelim(token::Brace)) {
+            self.parse_block_expr(None, lo, BlockCheckMode::Default, attrs)
+        } else if self.check(&token::BinOp(token::Or)) || self.check(&token::OrOr) {
+            self.parse_closure_expr(attrs)
+        } else if self.check(&token::OpenDelim(token::Bracket)) {
+            self.parse_array_or_repeat_expr(attrs)
+        } else if self.eat_lt() {
+            let (qself, path) = self.parse_qpath(PathStyle::Expr)?;
+            Ok(self.mk_expr(lo.to(path.span), ExprKind::Path(Some(qself), path), attrs))
+        } else if self.token.is_path_start() {
+            self.parse_path_start_expr(attrs)
+        } else if self.check_keyword(kw::Move) || self.check_keyword(kw::Static) {
+            self.parse_closure_expr(attrs)
+        } else if self.eat_keyword(kw::If) {
+            self.parse_if_expr(attrs)
+        } else if self.eat_keyword(kw::For) {
+            self.parse_for_expr(None, self.prev_span, attrs)
+        } else if self.eat_keyword(kw::While) {
+            self.parse_while_expr(None, self.prev_span, attrs)
+        } else if let Some(label) = self.eat_label() {
+            self.parse_labeled_expr(label, attrs)
+        } else if self.eat_keyword(kw::Loop) {
+            self.parse_loop_expr(None, self.prev_span, attrs)
+        } else if self.eat_keyword(kw::Continue) {
+            let kind = ExprKind::Continue(self.eat_label());
+            Ok(self.mk_expr(lo.to(self.prev_span), kind, attrs))
+        } else if self.eat_keyword(kw::Match) {
+            let match_sp = self.prev_span;
+            self.parse_match_expr(attrs).map_err(|mut err| {
+                err.span_label(match_sp, "while parsing this match expression");
+                err
+            })
+        } else if self.eat_keyword(kw::Unsafe) {
+            self.parse_block_expr(None, lo, BlockCheckMode::Unsafe(ast::UserProvided), attrs)
+        } else if self.is_do_catch_block() {
+            self.recover_do_catch(attrs)
+        } else if self.is_try_block() {
+            self.expect_keyword(kw::Try)?;
+            self.parse_try_block(lo, attrs)
+        } else if self.eat_keyword(kw::Return) {
+            self.parse_return_expr(attrs)
+        } else if self.eat_keyword(kw::Break) {
+            self.parse_break_expr(attrs)
+        } else if self.eat_keyword(kw::Yield) {
+            self.parse_yield_expr(attrs)
+        } else if self.eat_keyword(kw::Let) {
+            self.parse_let_expr(attrs)
+        } else if !self.unclosed_delims.is_empty() && self.check(&token::Semi) {
+            // Don't complain about bare semicolons after unclosed braces
+            // recovery in order to keep the error count down. Fixing the
+            // delimiters will possibly also fix the bare semicolon found in
+            // expression context. For example, silence the following error:
+            //
+            //     error: expected expression, found `;`
+            //      --> file.rs:2:13
+            //       |
+            //     2 |     foo(bar(;
+            //       |             ^ expected expression
+            self.bump();
+            Ok(self.mk_expr_err(self.token.span))
+        } else if self.token.span.rust_2018() {
+            // `Span::rust_2018()` is somewhat expensive; don't get it repeatedly.
+            if self.check_keyword(kw::Async) {
+                if self.is_async_block() { // Check for `async {` and `async move {`.
+                    self.parse_async_block(attrs)
+                } else {
+                    self.parse_closure_expr(attrs)
                 }
+            } else if self.eat_keyword(kw::Await) {
+                self.recover_incorrect_await_syntax(lo, self.prev_span, attrs)
+            } else {
+                self.parse_lit_expr(attrs)
             }
+        } else {
+            self.parse_lit_expr(attrs)
         }
+    }
 
-        // Note: when adding new syntax here, don't forget to adjust `TokenKind::can_begin_expr()`.
-        match self.token.kind {
-            // This match arm is a special-case of the `_` match arm below and
-            // could be removed without changing functionality, but it's faster
-            // to have it here, especially for programs with large constants.
-            token::Literal(_) => {
-                parse_lit!()
+    fn parse_lit_expr(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> {
+        let lo = self.token.span;
+        match self.parse_opt_lit() {
+            Some(literal) => {
+                let expr = self.mk_expr(lo.to(self.prev_span), ExprKind::Lit(literal), attrs);
+                self.maybe_recover_from_bad_qpath(expr, true)
             }
-            token::OpenDelim(token::Paren) => {
-                self.bump();
+            None => return Err(self.expected_expression_found()),
+        }
+    }
 
-                attrs.extend(self.parse_inner_attributes()?);
-
-                // `(e)` is parenthesized `e`.
-                // `(e,)` is a tuple with only one field, `e`.
-                let mut es = vec![];
-                let mut trailing_comma = false;
-                let mut recovered = false;
-                while self.token != token::CloseDelim(token::Paren) {
-                    es.push(match self.parse_expr() {
-                        Ok(es) => es,
-                        Err(mut err) => {
-                            // Recover from parse error in tuple list.
-                            match self.token.kind {
-                                token::Ident(name, false)
-                                if name == kw::Underscore && self.look_ahead(1, |t| {
-                                    t == &token::Comma
-                                }) => {
-                                    // Special-case handling of `Foo<(_, _, _)>`
-                                    err.emit();
-                                    let sp = self.token.span;
-                                    self.bump();
-                                    self.mk_expr(sp, ExprKind::Err, ThinVec::new())
-                                }
-                                _ => return Ok(
-                                    self.recover_seq_parse_error(token::Paren, lo, Err(err)),
-                                ),
-                            }
-                        }
-                    });
-                    recovered = self.expect_one_of(
-                        &[],
-                        &[token::Comma, token::CloseDelim(token::Paren)],
-                    )?;
-                    if self.eat(&token::Comma) {
-                        trailing_comma = true;
-                    } else {
-                        trailing_comma = false;
-                        break;
-                    }
-                }
-                if !recovered {
-                    self.bump();
-                }
+    fn parse_tuple_parens_expr(&mut self, mut attrs: AttrVec) -> PResult<'a, P<Expr>> {
+        let lo = self.token.span;
+        self.expect(&token::OpenDelim(token::Paren))?;
+        attrs.extend(self.parse_inner_attributes()?); // `(#![foo] a, b, ...)` is OK.
+        let (es, trailing_comma) = match self.parse_seq_to_end(
+            &token::CloseDelim(token::Paren),
+            SeqSep::trailing_allowed(token::Comma),
+            |p| p.parse_expr_catch_underscore(),
+        ) {
+            Ok(x) => x,
+            Err(err) => return Ok(self.recover_seq_parse_error(token::Paren, lo, Err(err))),
+        };
+        let kind = if es.len() == 1 && !trailing_comma {
+            // `(e)` is parenthesized `e`.
+            ExprKind::Paren(es.into_iter().nth(0).unwrap())
+        } else {
+            // `(e,)` is a tuple with only one field, `e`.
+            ExprKind::Tup(es)
+        };
+        let expr = self.mk_expr(lo.to(self.prev_span), kind, attrs);
+        self.maybe_recover_from_bad_qpath(expr, true)
+    }
 
-                hi = self.prev_span;
-                ex = if es.len() == 1 && !trailing_comma {
-                    ExprKind::Paren(es.into_iter().nth(0).unwrap())
-                } else {
-                    ExprKind::Tup(es)
+    fn parse_array_or_repeat_expr(&mut self, mut attrs: AttrVec) -> PResult<'a, P<Expr>> {
+        let lo = self.token.span;
+        self.bump(); // `[`
+
+        attrs.extend(self.parse_inner_attributes()?);
+
+        let close = &token::CloseDelim(token::Bracket);
+        let kind = if self.eat(close) {
+            // Empty vector
+            ExprKind::Array(Vec::new())
+        } else {
+            // Non-empty vector
+            let first_expr = self.parse_expr()?;
+            if self.eat(&token::Semi) {
+                // Repeating array syntax: `[ 0; 512 ]`
+                let count = AnonConst {
+                    id: DUMMY_NODE_ID,
+                    value: self.parse_expr()?,
                 };
+                self.expect(close)?;
+                ExprKind::Repeat(first_expr, count)
+            } else if self.eat(&token::Comma) {
+                // Vector with two or more elements.
+                let sep = SeqSep::trailing_allowed(token::Comma);
+                let (remaining_exprs, _) = self.parse_seq_to_end(close, sep, |p| p.parse_expr())?;
+                let mut exprs = vec![first_expr];
+                exprs.extend(remaining_exprs);
+                ExprKind::Array(exprs)
+            } else {
+                // Vector with one element
+                self.expect(close)?;
+                ExprKind::Array(vec![first_expr])
             }
-            token::OpenDelim(token::Brace) => {
-                return self.parse_block_expr(None, lo, BlockCheckMode::Default, attrs);
-            }
-            token::BinOp(token::Or) | token::OrOr => {
-                return self.parse_closure_expr(attrs);
+        };
+        let expr = self.mk_expr(lo.to(self.prev_span), kind, attrs);
+        self.maybe_recover_from_bad_qpath(expr, true)
+    }
+
+    fn parse_path_start_expr(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> {
+        let lo = self.token.span;
+        let path = self.parse_path(PathStyle::Expr)?;
+
+        // `!`, as an operator, is prefix, so we know this isn't that.
+        let (hi, kind) = if self.eat(&token::Not) {
+            // MACRO INVOCATION expression
+            let mac = Mac {
+                path,
+                args: self.parse_mac_args()?,
+                prior_type_ascription: self.last_type_ascription,
+            };
+            (self.prev_span, ExprKind::Mac(mac))
+        } else if self.check(&token::OpenDelim(token::Brace)) {
+            if let Some(expr) = self.maybe_parse_struct_expr(lo, &path, &attrs) {
+                return expr;
+            } else {
+                (path.span, ExprKind::Path(None, path))
             }
-            token::OpenDelim(token::Bracket) => {
-                self.bump();
+        } else {
+            (path.span, ExprKind::Path(None, path))
+        };
 
-                attrs.extend(self.parse_inner_attributes()?);
+        let expr = self.mk_expr(lo.to(hi), kind, attrs);
+        self.maybe_recover_from_bad_qpath(expr, true)
+    }
 
-                if self.eat(&token::CloseDelim(token::Bracket)) {
-                    // Empty vector
-                    ex = ExprKind::Array(Vec::new());
-                } else {
-                    // Non-empty vector
-                    let first_expr = self.parse_expr()?;
-                    if self.eat(&token::Semi) {
-                        // Repeating array syntax: `[ 0; 512 ]`
-                        let count = AnonConst {
-                            id: DUMMY_NODE_ID,
-                            value: self.parse_expr()?,
-                        };
-                        self.expect(&token::CloseDelim(token::Bracket))?;
-                        ex = ExprKind::Repeat(first_expr, count);
-                    } else if self.eat(&token::Comma) {
-                        // Vector with two or more elements
-                        let remaining_exprs = self.parse_seq_to_end(
-                            &token::CloseDelim(token::Bracket),
-                            SeqSep::trailing_allowed(token::Comma),
-                            |p| Ok(p.parse_expr()?)
-                        )?;
-                        let mut exprs = vec![first_expr];
-                        exprs.extend(remaining_exprs);
-                        ex = ExprKind::Array(exprs);
-                    } else {
-                        // Vector with one element
-                        self.expect(&token::CloseDelim(token::Bracket))?;
-                        ex = ExprKind::Array(vec![first_expr]);
-                    }
-                }
-                hi = self.prev_span;
-            }
-            _ => {
-                if self.eat_lt() {
-                    let (qself, path) = self.parse_qpath(PathStyle::Expr)?;
-                    hi = path.span;
-                    return Ok(self.mk_expr(lo.to(hi), ExprKind::Path(Some(qself), path), attrs));
-                }
-                if self.token.is_path_start() {
-                    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 args = self.parse_mac_args()?;
-                        hi = self.prev_span;
-                        ex = ExprKind::Mac(Mac {
-                            path,
-                            args,
-                            prior_type_ascription: self.last_type_ascription,
-                        });
-                    } else if self.check(&token::OpenDelim(token::Brace)) {
-                        if let Some(expr) = self.maybe_parse_struct_expr(lo, &path, &attrs) {
-                            return expr;
-                        } else {
-                            hi = path.span;
-                            ex = ExprKind::Path(None, path);
-                        }
-                    } else {
-                        hi = path.span;
-                        ex = ExprKind::Path(None, path);
-                    }
+    fn parse_labeled_expr(&mut self, label: Label, attrs: AttrVec) -> PResult<'a, P<Expr>> {
+        let lo = label.ident.span;
+        self.expect(&token::Colon)?;
+        if self.eat_keyword(kw::While) {
+            return self.parse_while_expr(Some(label), lo, attrs)
+        }
+        if self.eat_keyword(kw::For) {
+            return self.parse_for_expr(Some(label), lo, attrs)
+        }
+        if self.eat_keyword(kw::Loop) {
+            return self.parse_loop_expr(Some(label), lo, attrs)
+        }
+        if self.token == token::OpenDelim(token::Brace) {
+            return self.parse_block_expr(Some(label), lo, BlockCheckMode::Default, attrs);
+        }
 
-                    let expr = self.mk_expr(lo.to(hi), ex, attrs);
-                    return self.maybe_recover_from_bad_qpath(expr, true);
-                }
-                if self.check_keyword(kw::Move) || self.check_keyword(kw::Static) {
-                    return self.parse_closure_expr(attrs);
-                }
-                if self.eat_keyword(kw::If) {
-                    return self.parse_if_expr(attrs);
-                }
-                if self.eat_keyword(kw::For) {
-                    let lo = self.prev_span;
-                    return self.parse_for_expr(None, lo, attrs);
-                }
-                if self.eat_keyword(kw::While) {
-                    let lo = self.prev_span;
-                    return self.parse_while_expr(None, lo, attrs);
-                }
-                if let Some(label) = self.eat_label() {
-                    let lo = label.ident.span;
-                    self.expect(&token::Colon)?;
-                    if self.eat_keyword(kw::While) {
-                        return self.parse_while_expr(Some(label), lo, attrs)
-                    }
-                    if self.eat_keyword(kw::For) {
-                        return self.parse_for_expr(Some(label), lo, attrs)
-                    }
-                    if self.eat_keyword(kw::Loop) {
-                        return self.parse_loop_expr(Some(label), lo, attrs)
-                    }
-                    if self.token == token::OpenDelim(token::Brace) {
-                        return self.parse_block_expr(Some(label),
-                                                     lo,
-                                                     BlockCheckMode::Default,
-                                                     attrs);
-                    }
-                    let msg = "expected `while`, `for`, `loop` or `{` after a label";
-                    let mut err = self.fatal(msg);
-                    err.span_label(self.token.span, msg);
-                    return Err(err);
-                }
-                if self.eat_keyword(kw::Loop) {
-                    let lo = self.prev_span;
-                    return self.parse_loop_expr(None, lo, attrs);
-                }
-                if self.eat_keyword(kw::Continue) {
-                    let label = self.eat_label();
-                    let ex = ExprKind::Continue(label);
-                    let hi = self.prev_span;
-                    return Ok(self.mk_expr(lo.to(hi), ex, attrs));
-                }
-                if self.eat_keyword(kw::Match) {
-                    let match_sp = self.prev_span;
-                    return self.parse_match_expr(attrs).map_err(|mut err| {
-                        err.span_label(match_sp, "while parsing this match expression");
-                        err
-                    });
-                }
-                if self.eat_keyword(kw::Unsafe) {
-                    return self.parse_block_expr(
-                        None,
-                        lo,
-                        BlockCheckMode::Unsafe(ast::UserProvided),
-                        attrs);
-                }
-                if self.is_do_catch_block() {
-                    let mut db = self.fatal("found removed `do catch` syntax");
-                    db.help("following RFC #2388, the new non-placeholder syntax is `try`");
-                    return Err(db);
-                }
-                if self.is_try_block() {
-                    let lo = self.token.span;
-                    assert!(self.eat_keyword(kw::Try));
-                    return self.parse_try_block(lo, attrs);
-                }
+        let msg = "expected `while`, `for`, `loop` or `{` after a label";
+        self.struct_span_err(self.token.span, msg)
+            .span_label(self.token.span, msg)
+            .emit();
+        // Continue as an expression in an effort to recover on `'label: non_block_expr`.
+        self.parse_expr()
+    }
 
-                // `Span::rust_2018()` is somewhat expensive; don't get it repeatedly.
-                let is_span_rust_2018 = self.token.span.rust_2018();
-                if is_span_rust_2018 && self.check_keyword(kw::Async) {
-                    return if self.is_async_block() { // Check for `async {` and `async move {`.
-                        self.parse_async_block(attrs)
-                    } else {
-                        self.parse_closure_expr(attrs)
-                    };
-                }
-                if self.eat_keyword(kw::Return) {
-                    if self.token.can_begin_expr() {
-                        let e = self.parse_expr()?;
-                        hi = e.span;
-                        ex = ExprKind::Ret(Some(e));
-                    } else {
-                        ex = ExprKind::Ret(None);
-                    }
-                } else if self.eat_keyword(kw::Break) {
-                    let label = self.eat_label();
-                    let e = if self.token.can_begin_expr()
-                               && !(self.token == token::OpenDelim(token::Brace)
-                                    && self.restrictions.contains(
-                                           Restrictions::NO_STRUCT_LITERAL)) {
-                        Some(self.parse_expr()?)
-                    } else {
-                        None
-                    };
-                    ex = ExprKind::Break(label, e);
-                    hi = self.prev_span;
-                } else if self.eat_keyword(kw::Yield) {
-                    if self.token.can_begin_expr() {
-                        let e = self.parse_expr()?;
-                        hi = e.span;
-                        ex = ExprKind::Yield(Some(e));
-                    } else {
-                        ex = ExprKind::Yield(None);
-                    }
+    /// Recover on the syntax `do catch { ... }` suggesting `try { ... }` instead.
+    fn recover_do_catch(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> {
+        let lo = self.token.span;
 
-                    let span = lo.to(hi);
-                    self.sess.gated_spans.gate(sym::generators, span);
-                } else if self.eat_keyword(kw::Let) {
-                    return self.parse_let_expr(attrs);
-                } else if is_span_rust_2018 && self.eat_keyword(kw::Await) {
-                    let (await_hi, e_kind) = self.parse_incorrect_await_syntax(lo, self.prev_span)?;
-                    hi = await_hi;
-                    ex = e_kind;
-                } else {
-                    if !self.unclosed_delims.is_empty() && self.check(&token::Semi) {
-                        // Don't complain about bare semicolons after unclosed braces
-                        // recovery in order to keep the error count down. Fixing the
-                        // delimiters will possibly also fix the bare semicolon found in
-                        // expression context. For example, silence the following error:
-                        //
-                        //     error: expected expression, found `;`
-                        //      --> file.rs:2:13
-                        //       |
-                        //     2 |     foo(bar(;
-                        //       |             ^ expected expression
-                        self.bump();
-                        return Ok(self.mk_expr(self.token.span, ExprKind::Err, ThinVec::new()));
-                    }
-                    parse_lit!()
-                }
-            }
-        }
+        self.bump(); // `do`
+        self.bump(); // `catch`
+
+        let span_dc = lo.to(self.prev_span);
+        self.struct_span_err(span_dc, "found removed `do catch` syntax")
+            .span_suggestion(
+                span_dc,
+                "replace with the new syntax",
+                "try".to_string(),
+                Applicability::MachineApplicable,
+            )
+            .note("following RFC #2388, the new non-placeholder syntax is `try`")
+            .emit();
 
-        let expr = self.mk_expr(lo.to(hi), ex, attrs);
+        self.parse_try_block(lo, attrs)
+    }
+
+    /// Parse an expression if the token can begin one.
+    fn parse_expr_opt(&mut self) -> PResult<'a, Option<P<Expr>>> {
+        Ok(if self.token.can_begin_expr() {
+            Some(self.parse_expr()?)
+        } else {
+            None
+        })
+    }
+
+    /// Parse `"return" expr?`.
+    fn parse_return_expr(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> {
+        let lo = self.prev_span;
+        let kind = ExprKind::Ret(self.parse_expr_opt()?);
+        let expr = self.mk_expr(lo.to(self.prev_span), kind, attrs);
+        self.maybe_recover_from_bad_qpath(expr, true)
+    }
+
+    /// Parse `"('label ":")? break expr?`.
+    fn parse_break_expr(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> {
+        let lo = self.prev_span;
+        let label = self.eat_label();
+        let kind = if self.token != token::OpenDelim(token::Brace)
+            || !self.restrictions.contains(Restrictions::NO_STRUCT_LITERAL)
+        {
+            self.parse_expr_opt()?
+        } else {
+            None
+        };
+        let expr = self.mk_expr(lo.to(self.prev_span), ExprKind::Break(label, kind), attrs);
+        self.maybe_recover_from_bad_qpath(expr, true)
+    }
+
+    /// Parse `"yield" expr?`.
+    fn parse_yield_expr(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> {
+        let lo = self.prev_span;
+        let kind = ExprKind::Yield(self.parse_expr_opt()?);
+        let span = lo.to(self.prev_span);
+        self.sess.gated_spans.gate(sym::generators, span);
+        let expr = self.mk_expr(span, kind, attrs);
         self.maybe_recover_from_bad_qpath(expr, true)
     }
 
@@ -1282,12 +1291,12 @@ impl<'a> Parser<'a> {
         let lo = self.token.span;
         let literal = self.parse_lit()?;
         let hi = self.prev_span;
-        let expr = self.mk_expr(lo.to(hi), ExprKind::Lit(literal), ThinVec::new());
+        let expr = self.mk_expr(lo.to(hi), ExprKind::Lit(literal), AttrVec::new());
 
         if minus_present {
             let minus_hi = self.prev_span;
             let unary = self.mk_unary(UnOp::Neg, expr);
-            Ok(self.mk_expr(minus_lo.to(minus_hi), unary, ThinVec::new()))
+            Ok(self.mk_expr(minus_lo.to(minus_hi), unary, AttrVec::new()))
         } else {
             Ok(expr)
         }
@@ -1299,7 +1308,7 @@ impl<'a> Parser<'a> {
         opt_label: Option<Label>,
         lo: Span,
         blk_mode: BlockCheckMode,
-        outer_attrs: ThinVec<Attribute>,
+        outer_attrs: AttrVec,
     ) -> PResult<'a, P<Expr>> {
         if let Some(label) = opt_label {
             self.sess.gated_spans.gate(sym::label_break_value, label.ident.span);
@@ -1315,7 +1324,7 @@ impl<'a> Parser<'a> {
     }
 
     /// Parses a closure expression (e.g., `move |args| expr`).
-    fn parse_closure_expr(&mut self, attrs: ThinVec<Attribute>) -> PResult<'a, P<Expr>> {
+    fn parse_closure_expr(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> {
         let lo = self.token.span;
 
         let movability = if self.eat_keyword(kw::Static) {
@@ -1345,7 +1354,7 @@ impl<'a> Parser<'a> {
             _ => {
                 // If an explicit return type is given, require a block to appear (RFC 968).
                 let body_lo = self.token.span;
-                self.parse_block_expr(None, body_lo, BlockCheckMode::Default, ThinVec::new())?
+                self.parse_block_expr(None, body_lo, BlockCheckMode::Default, AttrVec::new())?
             }
         };
 
@@ -1415,7 +1424,7 @@ impl<'a> Parser<'a> {
     }
 
     /// Parses an `if` expression (`if` token already eaten).
-    fn parse_if_expr(&mut self, attrs: ThinVec<Attribute>) -> PResult<'a, P<Expr>> {
+    fn parse_if_expr(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> {
         let lo = self.prev_span;
         let cond = self.parse_cond_expr()?;
 
@@ -1461,7 +1470,7 @@ impl<'a> Parser<'a> {
 
     /// Parses a `let $pat = $expr` pseudo-expression.
     /// The `let` token has already been eaten.
-    fn parse_let_expr(&mut self, attrs: ThinVec<Attribute>) -> PResult<'a, P<Expr>> {
+    fn parse_let_expr(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> {
         let lo = self.prev_span;
         let pat = self.parse_top_pat(GateOr::No)?;
         self.expect(&token::Eq)?;
@@ -1477,10 +1486,10 @@ impl<'a> Parser<'a> {
     /// Parses an `else { ... }` expression (`else` token already eaten).
     fn parse_else_expr(&mut self) -> PResult<'a, P<Expr>> {
         if self.eat_keyword(kw::If) {
-            return self.parse_if_expr(ThinVec::new());
+            return self.parse_if_expr(AttrVec::new());
         } else {
             let blk = self.parse_block()?;
-            return Ok(self.mk_expr(blk.span, ExprKind::Block(blk, None), ThinVec::new()));
+            return Ok(self.mk_expr(blk.span, ExprKind::Block(blk, None), AttrVec::new()));
         }
     }
 
@@ -1489,7 +1498,7 @@ impl<'a> Parser<'a> {
         &mut self,
         opt_label: Option<Label>,
         span_lo: Span,
-        mut attrs: ThinVec<Attribute>
+        mut attrs: AttrVec
     ) -> PResult<'a, P<Expr>> {
         // Parse: `for <src_pat> in <src_expr> <src_loop_block>`
 
@@ -1531,7 +1540,7 @@ impl<'a> Parser<'a> {
         &mut self,
         opt_label: Option<Label>,
         span_lo: Span,
-        mut attrs: ThinVec<Attribute>
+        mut attrs: AttrVec
     ) -> PResult<'a, P<Expr>> {
         let cond = self.parse_cond_expr()?;
         let (iattrs, body) = self.parse_inner_attrs_and_block()?;
@@ -1545,7 +1554,7 @@ impl<'a> Parser<'a> {
         &mut self,
         opt_label: Option<Label>,
         span_lo: Span,
-        mut attrs: ThinVec<Attribute>
+        mut attrs: AttrVec
     ) -> PResult<'a, P<Expr>> {
         let (iattrs, body) = self.parse_inner_attrs_and_block()?;
         attrs.extend(iattrs);
@@ -1564,7 +1573,7 @@ impl<'a> Parser<'a> {
     }
 
     /// Parses a `match ... { ... }` expression (`match` token already eaten).
-    fn parse_match_expr(&mut self, mut attrs: ThinVec<Attribute>) -> PResult<'a, P<Expr>> {
+    fn parse_match_expr(&mut self, mut attrs: AttrVec) -> PResult<'a, P<Expr>> {
         let match_span = self.prev_span;
         let lo = self.prev_span;
         let discriminant = self.parse_expr_res(Restrictions::NO_STRUCT_LITERAL, None)?;
@@ -1676,11 +1685,7 @@ impl<'a> Parser<'a> {
     }
 
     /// Parses a `try {...}` expression (`try` token already eaten).
-    fn parse_try_block(
-        &mut self,
-        span_lo: Span,
-        mut attrs: ThinVec<Attribute>
-    ) -> PResult<'a, P<Expr>> {
+    fn parse_try_block(&mut self, span_lo: Span, mut attrs: AttrVec) -> PResult<'a, P<Expr>> {
         let (iattrs, body) = self.parse_inner_attrs_and_block()?;
         attrs.extend(iattrs);
         if self.eat_keyword(kw::Catch) {
@@ -1712,7 +1717,7 @@ impl<'a> Parser<'a> {
     }
 
     /// Parses an `async move? {...}` expression.
-    fn parse_async_block(&mut self, mut attrs: ThinVec<Attribute>) -> PResult<'a, P<Expr>> {
+    fn parse_async_block(&mut self, mut attrs: AttrVec) -> PResult<'a, P<Expr>> {
         let span_lo = self.token.span;
         self.expect_keyword(kw::Async)?;
         let capture_clause = self.parse_capture_clause();
@@ -1739,7 +1744,7 @@ impl<'a> Parser<'a> {
         &mut self,
         lo: Span,
         path: &ast::Path,
-        attrs: &ThinVec<Attribute>,
+        attrs: &AttrVec,
     ) -> Option<PResult<'a, P<Expr>>> {
         let struct_allowed = !self.restrictions.contains(Restrictions::NO_STRUCT_LITERAL);
         let certainly_not_a_block = || self.look_ahead(1, |t| t.is_ident()) && (
@@ -1780,7 +1785,7 @@ impl<'a> Parser<'a> {
         &mut self,
         lo: Span,
         pth: ast::Path,
-        mut attrs: ThinVec<Attribute>
+        mut attrs: AttrVec
     ) -> PResult<'a, P<Expr>> {
         let struct_sp = lo.to(self.prev_span);
         self.bump();
@@ -1826,9 +1831,9 @@ impl<'a> Parser<'a> {
                     recovery_field = Some(ast::Field {
                         ident: Ident::new(name, self.token.span),
                         span: self.token.span,
-                        expr: self.mk_expr(self.token.span, ExprKind::Err, ThinVec::new()),
+                        expr: self.mk_expr(self.token.span, ExprKind::Err, AttrVec::new()),
                         is_shorthand: false,
-                        attrs: ThinVec::new(),
+                        attrs: AttrVec::new(),
                         id: DUMMY_NODE_ID,
                         is_placeholder: false,
                     });
@@ -1907,7 +1912,7 @@ impl<'a> Parser<'a> {
 
             // Mimic `x: x` for the `x` field shorthand.
             let path = ast::Path::from_ident(fieldname);
-            let expr = self.mk_expr(fieldname.span, ExprKind::Path(None, path), ThinVec::new());
+            let expr = self.mk_expr(fieldname.span, ExprKind::Path(None, path), AttrVec::new());
             (fieldname, expr, true)
         };
         Ok(ast::Field {
@@ -1984,16 +1989,16 @@ impl<'a> Parser<'a> {
 
     fn mk_await_expr(&mut self, self_arg: P<Expr>, lo: Span) -> PResult<'a, P<Expr>> {
         let span = lo.to(self.prev_span);
-        let await_expr = self.mk_expr(span, ExprKind::Await(self_arg), ThinVec::new());
+        let await_expr = self.mk_expr(span, ExprKind::Await(self_arg), AttrVec::new());
         self.recover_from_await_method_call();
         Ok(await_expr)
     }
 
-    crate fn mk_expr(&self, span: Span, kind: ExprKind, attrs: ThinVec<Attribute>) -> P<Expr> {
+    crate fn mk_expr(&self, span: Span, kind: ExprKind, attrs: AttrVec) -> P<Expr> {
         P(Expr { kind, span, attrs, id: DUMMY_NODE_ID })
     }
 
     pub(super) fn mk_expr_err(&self, span: Span) -> P<Expr> {
-        self.mk_expr(span, ExprKind::Err, ThinVec::new())
+        self.mk_expr(span, ExprKind::Err, AttrVec::new())
     }
 }
diff --git a/src/librustc_parse/parser/item.rs b/src/librustc_parse/parser/item.rs
index 0840a1551db..229ca07f13b 100644
--- a/src/librustc_parse/parser/item.rs
+++ b/src/librustc_parse/parser/item.rs
@@ -5,15 +5,14 @@ use crate::maybe_whole;
 
 use rustc_errors::{PResult, Applicability, DiagnosticBuilder, StashKey};
 use rustc_error_codes::*;
-use syntax::ast::{self, DUMMY_NODE_ID, Ident, Attribute, AttrKind, AttrStyle, AnonConst, Item};
-use syntax::ast::{AssocItem, AssocItemKind, ItemKind, UseTree, UseTreeKind};
+use syntax::ast::{self, DUMMY_NODE_ID, Ident, AttrVec, Attribute, AttrKind, AttrStyle, AnonConst};
+use syntax::ast::{AssocItem, AssocItemKind, Item, ItemKind, UseTree, UseTreeKind};
 use syntax::ast::{PathSegment, IsAuto, Constness, IsAsync, Unsafety, Defaultness, Extern, StrLit};
 use syntax::ast::{Visibility, VisibilityKind, Mutability, FnHeader, ForeignItem, ForeignItemKind};
 use syntax::ast::{Ty, TyKind, Generics, TraitRef, EnumDef, Variant, VariantData, StructField};
 use syntax::ast::{Mac, MacArgs, MacDelimiter, Block, BindingMode, FnDecl, FnSig, SelfKind, Param};
 use syntax::print::pprust;
 use syntax::ptr::P;
-use syntax::ThinVec;
 use syntax::token;
 use syntax::tokenstream::{DelimSpan, TokenTree, TokenStream};
 use syntax::struct_span_err;
@@ -2095,7 +2094,7 @@ impl<'a> Parser<'a> {
         };
 
         let eself = source_map::respan(eself_lo.to(eself_hi), eself);
-        Ok(Some(Param::from_self(ThinVec::default(), eself, eself_ident)))
+        Ok(Some(Param::from_self(AttrVec::default(), eself, eself_ident)))
     }
 
     fn is_named_param(&self) -> bool {
diff --git a/src/librustc_parse/parser/mod.rs b/src/librustc_parse/parser/mod.rs
index 07e99cfe012..255e789b58e 100644
--- a/src/librustc_parse/parser/mod.rs
+++ b/src/librustc_parse/parser/mod.rs
@@ -15,9 +15,8 @@ use crate::{Directory, DirectoryOwnership};
 use crate::lexer::UnmatchedBrace;
 
 use rustc_errors::{PResult, Applicability, DiagnosticBuilder, FatalError};
-use rustc_data_structures::thin_vec::ThinVec;
-use syntax::ast::{self, DUMMY_NODE_ID, AttrStyle, Attribute, CrateSugar, Extern, Ident, StrLit};
-use syntax::ast::{IsAsync, MacArgs, MacDelimiter, Mutability, Visibility, VisibilityKind, Unsafety};
+use syntax::ast::{self, DUMMY_NODE_ID, AttrVec, AttrStyle, CrateSugar, Extern, Ident, Unsafety};
+use syntax::ast::{StrLit, IsAsync, MacArgs, MacDelimiter, Mutability, Visibility, VisibilityKind};
 use syntax::print::pprust;
 use syntax::ptr::P;
 use syntax::token::{self, Token, TokenKind, DelimToken};
@@ -740,34 +739,6 @@ impl<'a> Parser<'a> {
         }
     }
 
-    /// Parses a sequence, including the closing delimiter. The function
-    /// `f` must consume tokens until reaching the next separator or
-    /// closing bracket.
-    fn parse_seq_to_end<T>(
-        &mut self,
-        ket: &TokenKind,
-        sep: SeqSep,
-        f: impl FnMut(&mut Parser<'a>) -> PResult<'a,  T>,
-    ) -> PResult<'a, Vec<T>> {
-        let (val, _, recovered) = self.parse_seq_to_before_end(ket, sep, f)?;
-        if !recovered {
-            self.bump();
-        }
-        Ok(val)
-    }
-
-    /// Parses a sequence, not including the closing delimiter. The function
-    /// `f` must consume tokens until reaching the next separator or
-    /// closing bracket.
-    fn parse_seq_to_before_end<T>(
-        &mut self,
-        ket: &TokenKind,
-        sep: SeqSep,
-        f: impl FnMut(&mut Parser<'a>) -> PResult<'a, T>,
-    ) -> PResult<'a, (Vec<T>, bool, bool)> {
-        self.parse_seq_to_before_tokens(&[ket], sep, TokenExpectType::Expect, f)
-    }
-
     fn expect_any_with_type(&mut self, kets: &[&TokenKind], expect: TokenExpectType) -> bool {
         kets.iter().any(|k| {
             match expect {
@@ -855,6 +826,34 @@ impl<'a> Parser<'a> {
         Ok((v, trailing, recovered))
     }
 
+    /// Parses a sequence, not including the closing delimiter. The function
+    /// `f` must consume tokens until reaching the next separator or
+    /// closing bracket.
+    fn parse_seq_to_before_end<T>(
+        &mut self,
+        ket: &TokenKind,
+        sep: SeqSep,
+        f: impl FnMut(&mut Parser<'a>) -> PResult<'a, T>,
+    ) -> PResult<'a, (Vec<T>, bool, bool)> {
+        self.parse_seq_to_before_tokens(&[ket], sep, TokenExpectType::Expect, f)
+    }
+
+    /// Parses a sequence, including the closing delimiter. The function
+    /// `f` must consume tokens until reaching the next separator or
+    /// closing bracket.
+    fn parse_seq_to_end<T>(
+        &mut self,
+        ket: &TokenKind,
+        sep: SeqSep,
+        f: impl FnMut(&mut Parser<'a>) -> PResult<'a,  T>,
+    ) -> PResult<'a, (Vec<T>, bool /* trailing */)> {
+        let (val, trailing, recovered) = self.parse_seq_to_before_end(ket, sep, f)?;
+        if !recovered {
+            self.eat(ket);
+        }
+        Ok((val, trailing))
+    }
+
     /// Parses a sequence, including the closing delimiter. The function
     /// `f` must consume tokens until reaching the next separator or
     /// closing bracket.
@@ -866,11 +865,7 @@ impl<'a> Parser<'a> {
         f: impl FnMut(&mut Parser<'a>) -> PResult<'a, T>,
     ) -> PResult<'a, (Vec<T>, bool)> {
         self.expect(bra)?;
-        let (result, trailing, recovered) = self.parse_seq_to_before_end(ket, sep, f)?;
-        if !recovered {
-            self.eat(ket);
-        }
-        Ok((result, trailing))
+        self.parse_seq_to_end(ket, sep, f)
     }
 
     fn parse_delim_comma_seq<T>(
@@ -1054,8 +1049,8 @@ impl<'a> Parser<'a> {
 
     fn parse_or_use_outer_attributes(
         &mut self,
-        already_parsed_attrs: Option<ThinVec<Attribute>>,
-    ) -> PResult<'a, ThinVec<Attribute>> {
+        already_parsed_attrs: Option<AttrVec>,
+    ) -> PResult<'a, AttrVec> {
         if let Some(attrs) = already_parsed_attrs {
             Ok(attrs)
         } else {
diff --git a/src/librustc_parse/parser/pat.rs b/src/librustc_parse/parser/pat.rs
index 117b92dc9a5..593fb30bb75 100644
--- a/src/librustc_parse/parser/pat.rs
+++ b/src/librustc_parse/parser/pat.rs
@@ -1,12 +1,11 @@
 use super::{Parser, PathStyle};
 use crate::{maybe_recover_from_interpolated_ty_qpath, maybe_whole};
 use rustc_errors::{PResult, Applicability, DiagnosticBuilder};
-use syntax::ast::{self, Attribute, Pat, PatKind, FieldPat, RangeEnd, RangeSyntax, Mac};
+use syntax::ast::{self, AttrVec, Attribute, Pat, PatKind, FieldPat, RangeEnd, RangeSyntax, Mac};
 use syntax::ast::{BindingMode, Ident, Mutability, Path, QSelf, Expr, ExprKind};
 use syntax::mut_visit::{noop_visit_pat, noop_visit_mac, MutVisitor};
 use syntax::ptr::P;
 use syntax::print::pprust;
-use syntax::ThinVec;
 use syntax::token;
 use syntax_pos::source_map::{respan, Span, Spanned};
 use syntax_pos::symbol::{kw, sym};
@@ -636,7 +635,7 @@ impl<'a> Parser<'a> {
         let op_span = self.token.span;
         // Parse range
         let span = lo.to(self.prev_span);
-        let begin = self.mk_expr(span, ExprKind::Path(qself, path), ThinVec::new());
+        let begin = self.mk_expr(span, ExprKind::Path(qself, path), AttrVec::new());
         self.bump();
         let end = self.parse_pat_range_end_opt(&begin, form)?;
         Ok(PatKind::Range(begin, end, respan(op_span, end_kind)))
@@ -693,7 +692,7 @@ impl<'a> Parser<'a> {
         let lo = self.prev_span;
         let end = self.parse_pat_range_end()?;
         let range_span = lo.to(end.span);
-        let begin = self.mk_expr(range_span, ExprKind::Err, ThinVec::new());
+        let begin = self.mk_expr(range_span, ExprKind::Err, AttrVec::new());
 
         self.diagnostic()
             .struct_span_err(range_span, &format!("`{}X` range patterns are not supported", form))
@@ -731,7 +730,7 @@ impl<'a> Parser<'a> {
                 )
                 .emit();
 
-            Ok(self.mk_expr(range_span, ExprKind::Err, ThinVec::new()))
+            Ok(self.mk_expr(range_span, ExprKind::Err, AttrVec::new()))
         }
     }
 
@@ -747,7 +746,7 @@ impl<'a> Parser<'a> {
                 (None, self.parse_path(PathStyle::Expr)?)
             };
             let hi = self.prev_span;
-            Ok(self.mk_expr(lo.to(hi), ExprKind::Path(qself, path), ThinVec::new()))
+            Ok(self.mk_expr(lo.to(hi), ExprKind::Path(qself, path), AttrVec::new()))
         } else {
             self.parse_literal_maybe_minus()
         }
diff --git a/src/librustc_parse/parser/path.rs b/src/librustc_parse/parser/path.rs
index aeba6dd2f67..802d38e2997 100644
--- a/src/librustc_parse/parser/path.rs
+++ b/src/librustc_parse/parser/path.rs
@@ -3,7 +3,6 @@ use crate::maybe_whole;
 use rustc_errors::{PResult, Applicability, pluralize};
 use syntax::ast::{self, QSelf, Path, PathSegment, Ident, ParenthesizedArgs, AngleBracketedArgs};
 use syntax::ast::{AnonConst, GenericArg, AssocTyConstraint, AssocTyConstraintKind, BlockCheckMode};
-use syntax::ThinVec;
 use syntax::token::{self, Token};
 use syntax_pos::source_map::{Span, BytePos};
 use syntax_pos::symbol::{kw, sym};
@@ -400,7 +399,7 @@ impl<'a> Parser<'a> {
                 // Parse const argument.
                 let expr = if let token::OpenDelim(token::Brace) = self.token.kind {
                     self.parse_block_expr(
-                        None, self.token.span, BlockCheckMode::Default, ThinVec::new()
+                        None, self.token.span, BlockCheckMode::Default, ast::AttrVec::new()
                     )?
                 } else if self.token.is_ident() {
                     // FIXME(const_generics): to distinguish between idents for types and consts,
diff --git a/src/librustc_parse/parser/stmt.rs b/src/librustc_parse/parser/stmt.rs
index 943b6ecc825..42d85e96aef 100644
--- a/src/librustc_parse/parser/stmt.rs
+++ b/src/librustc_parse/parser/stmt.rs
@@ -7,15 +7,14 @@ use crate::maybe_whole;
 use crate::DirectoryOwnership;
 
 use rustc_errors::{PResult, Applicability};
-use syntax::ThinVec;
 use syntax::ptr::P;
 use syntax::ast;
 use syntax::ast::{DUMMY_NODE_ID, Stmt, StmtKind, Local, Block, BlockCheckMode, Expr, ExprKind};
-use syntax::ast::{Attribute, AttrStyle, VisibilityKind, MacStmtStyle, Mac};
+use syntax::ast::{AttrVec, Attribute, AttrStyle, VisibilityKind, MacStmtStyle, Mac};
 use syntax::util::classify;
 use syntax::token;
 use syntax_pos::source_map::{respan, Span};
-use syntax_pos::symbol::{kw, sym};
+use syntax_pos::symbol::{kw, sym, Symbol};
 
 use std::mem;
 
@@ -23,15 +22,11 @@ impl<'a> Parser<'a> {
     /// Parses a statement. This stops just before trailing semicolons on everything but items.
     /// e.g., a `StmtKind::Semi` parses to a `StmtKind::Expr`, leaving the trailing `;` unconsumed.
     pub fn parse_stmt(&mut self) -> PResult<'a, Option<Stmt>> {
-        Ok(self.parse_stmt_(true))
-    }
-
-    fn parse_stmt_(&mut self, macro_legacy_warnings: bool) -> Option<Stmt> {
-        self.parse_stmt_without_recovery(macro_legacy_warnings).unwrap_or_else(|mut e| {
+        Ok(self.parse_stmt_without_recovery(true).unwrap_or_else(|mut e| {
             e.emit();
             self.recover_stmt_(SemiColonMode::Break, BlockMode::Ignore);
             None
-        })
+        }))
     }
 
     fn parse_stmt_without_recovery(
@@ -43,171 +38,195 @@ impl<'a> Parser<'a> {
         let attrs = self.parse_outer_attributes()?;
         let lo = self.token.span;
 
-        Ok(Some(if self.eat_keyword(kw::Let) {
-            Stmt {
-                id: DUMMY_NODE_ID,
-                kind: StmtKind::Local(self.parse_local(attrs.into())?),
-                span: lo.to(self.prev_span),
-            }
-        } else if let Some(macro_def) = self.eat_macro_def(
-            &attrs,
-            &respan(lo, VisibilityKind::Inherited),
-            lo,
-        )? {
-            Stmt {
-                id: DUMMY_NODE_ID,
-                kind: StmtKind::Item(macro_def),
-                span: lo.to(self.prev_span),
-            }
+        if self.eat_keyword(kw::Let) {
+            return self.parse_local_mk(lo, attrs.into()).map(Some)
+        }
+        if self.is_kw_followed_by_ident(kw::Mut) {
+            return self.recover_stmt_local(lo, attrs.into(), "missing keyword", "let mut");
+        }
+        if self.is_kw_followed_by_ident(kw::Auto) {
+            self.bump(); // `auto`
+            let msg = "write `let` instead of `auto` to introduce a new variable";
+            return self.recover_stmt_local(lo, attrs.into(), msg, "let");
+        }
+        if self.is_kw_followed_by_ident(sym::var) {
+            self.bump(); // `var`
+            let msg = "write `let` instead of `var` to introduce a new variable";
+            return self.recover_stmt_local(lo, attrs.into(), msg, "let");
+        }
+
+        let mac_vis = respan(lo, VisibilityKind::Inherited);
+        if let Some(macro_def) = self.eat_macro_def(&attrs, &mac_vis, lo)? {
+            return Ok(Some(self.mk_stmt(lo.to(self.prev_span), StmtKind::Item(macro_def))));
+        }
+
         // Starts like a simple path, being careful to avoid contextual keywords
         // such as a union items, item with `crate` visibility or auto trait items.
         // Our goal here is to parse an arbitrary path `a::b::c` but not something that starts
         // like a path (1 token), but it fact not a path.
-        // `union::b::c` - path, `union U { ... }` - not a path.
-        // `crate::b::c` - path, `crate struct S;` - not a path.
-        } else if self.token.is_path_start() &&
-                  !self.token.is_qpath_start() &&
-                  !self.is_union_item() &&
-                  !self.is_crate_vis() &&
-                  !self.is_auto_trait_item() &&
-                  !self.is_async_fn() {
+        if self.token.is_path_start()
+            && !self.token.is_qpath_start()
+            && !self.is_union_item() // `union::b::c` - path, `union U { ... }` - not a path.
+            && !self.is_crate_vis() // `crate::b::c` - path, `crate struct S;` - not a path.
+            && !self.is_auto_trait_item()
+            && !self.is_async_fn()
+        {
             let path = self.parse_path(PathStyle::Expr)?;
 
-            if !self.eat(&token::Not) {
-                let expr = if self.check(&token::OpenDelim(token::Brace)) {
-                    self.parse_struct_expr(lo, path, ThinVec::new())?
-                } else {
-                    let hi = self.prev_span;
-                    self.mk_expr(lo.to(hi), ExprKind::Path(None, path), ThinVec::new())
-                };
-
-                let expr = self.with_res(Restrictions::STMT_EXPR, |this| {
-                    let expr = this.parse_dot_or_call_expr_with(expr, lo, attrs.into())?;
-                    this.parse_assoc_expr_with(0, LhsExpr::AlreadyParsed(expr))
-                })?;
-
-                return Ok(Some(Stmt {
-                    id: DUMMY_NODE_ID,
-                    kind: StmtKind::Expr(expr),
-                    span: lo.to(self.prev_span),
-                }));
+            if self.eat(&token::Not) {
+                return self.parse_stmt_mac(lo, attrs.into(), path, macro_legacy_warnings);
             }
 
-            let args = self.parse_mac_args()?;
-            let delim = args.delim();
-            let hi = self.prev_span;
-
-            let style = if delim == token::Brace {
-                MacStmtStyle::Braces
+            let expr = if self.check(&token::OpenDelim(token::Brace)) {
+                self.parse_struct_expr(lo, path, AttrVec::new())?
             } else {
-                MacStmtStyle::NoBraces
+                let hi = self.prev_span;
+                self.mk_expr(lo.to(hi), ExprKind::Path(None, path), AttrVec::new())
             };
 
-            let mac = Mac {
-                path,
-                args,
-                prior_type_ascription: self.last_type_ascription,
-            };
-            let kind = if delim == token::Brace ||
-                          self.token == token::Semi || self.token == token::Eof {
-                StmtKind::Mac(P((mac, style, attrs.into())))
-            }
-            // We used to incorrectly stop parsing macro-expanded statements here.
-            // If the next token will be an error anyway but could have parsed with the
-            // earlier behavior, stop parsing here and emit a warning to avoid breakage.
-            else if macro_legacy_warnings && self.token.can_begin_expr() &&
-                match self.token.kind {
-                    // These can continue an expression, so we can't stop parsing and warn.
-                    token::OpenDelim(token::Paren) | token::OpenDelim(token::Bracket) |
-                    token::BinOp(token::Minus) | token::BinOp(token::Star) |
-                    token::BinOp(token::And) | token::BinOp(token::Or) |
-                    token::AndAnd | token::OrOr |
-                    token::DotDot | token::DotDotDot | token::DotDotEq => false,
-                    _ => true,
-                }
-            {
-                self.warn_missing_semicolon();
-                StmtKind::Mac(P((mac, style, attrs.into())))
-            } else {
-                let e = self.mk_expr(lo.to(hi), 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)
-            };
-            Stmt {
-                id: DUMMY_NODE_ID,
-                span: lo.to(hi),
-                kind,
+            let expr = self.with_res(Restrictions::STMT_EXPR, |this| {
+                let expr = this.parse_dot_or_call_expr_with(expr, lo, attrs.into())?;
+                this.parse_assoc_expr_with(0, LhsExpr::AlreadyParsed(expr))
+            })?;
+            return Ok(Some(self.mk_stmt(lo.to(self.prev_span), StmtKind::Expr(expr))));
+        }
+
+        // FIXME: Bad copy of attrs
+        let old_directory_ownership =
+            mem::replace(&mut self.directory.ownership, DirectoryOwnership::UnownedViaBlock);
+        let item = self.parse_item_(attrs.clone(), false, true)?;
+        self.directory.ownership = old_directory_ownership;
+
+        if let Some(item) = item {
+            return Ok(Some(self.mk_stmt(lo.to(item.span), StmtKind::Item(item))));
+        }
+
+        // Do not attempt to parse an expression if we're done here.
+        if self.token == token::Semi {
+            self.error_outer_attrs(&attrs);
+            self.bump();
+            let mut last_semi = lo;
+            while self.token == token::Semi {
+                last_semi = self.token.span;
+                self.bump();
             }
+            // We are encoding a string of semicolons as an an empty tuple that spans
+            // the excess semicolons to preserve this info until the lint stage.
+            let kind = StmtKind::Semi(self.mk_expr(
+                lo.to(last_semi),
+                ExprKind::Tup(Vec::new()),
+                AttrVec::new()
+            ));
+            return Ok(Some(self.mk_stmt(lo.to(last_semi), kind)));
+        }
+
+        if self.token == token::CloseDelim(token::Brace) {
+            self.error_outer_attrs(&attrs);
+            return Ok(None);
+        }
+
+        // Remainder are line-expr stmts.
+        let e = self.parse_expr_res(Restrictions::STMT_EXPR, Some(attrs.into()))?;
+        Ok(Some(self.mk_stmt(lo.to(e.span), StmtKind::Expr(e))))
+    }
+
+    /// Parses a statement macro `mac!(args)` provided a `path` representing `mac`.
+    /// At this point, the `!` token after the path has already been eaten.
+    fn parse_stmt_mac(
+        &mut self,
+        lo: Span,
+        attrs: AttrVec,
+        path: ast::Path,
+        legacy_warnings: bool,
+    ) -> PResult<'a, Option<Stmt>> {
+        let args = self.parse_mac_args()?;
+        let delim = args.delim();
+        let hi = self.prev_span;
+
+        let style = if delim == token::Brace {
+            MacStmtStyle::Braces
         } else {
-            // FIXME: Bad copy of attrs
-            let old_directory_ownership =
-                mem::replace(&mut self.directory.ownership, DirectoryOwnership::UnownedViaBlock);
-            let item = self.parse_item_(attrs.clone(), false, true)?;
-            self.directory.ownership = old_directory_ownership;
-
-            match item {
-                Some(i) => Stmt {
-                    id: DUMMY_NODE_ID,
-                    span: lo.to(i.span),
-                    kind: StmtKind::Item(i),
-                },
-                None => {
-                    let unused_attrs = |attrs: &[Attribute], s: &mut Self| {
-                        if !attrs.is_empty() {
-                            if s.prev_token_kind == PrevTokenKind::DocComment {
-                                s.span_fatal_err(s.prev_span, Error::UselessDocComment).emit();
-                            } else if attrs.iter().any(|a| a.style == AttrStyle::Outer) {
-                                s.span_err(
-                                    s.token.span, "expected statement after outer attribute"
-                                );
-                            }
-                        }
-                    };
-
-                    // Do not attempt to parse an expression if we're done here.
-                    if self.token == token::Semi {
-                        unused_attrs(&attrs, self);
-                        self.bump();
-                        let mut last_semi = lo;
-                        while self.token == token::Semi {
-                            last_semi = self.token.span;
-                            self.bump();
-                        }
-                        // We are encoding a string of semicolons as an
-                        // an empty tuple that spans the excess semicolons
-                        // to preserve this info until the lint stage
-                        return Ok(Some(Stmt {
-                            id: DUMMY_NODE_ID,
-                            span: lo.to(last_semi),
-                            kind: StmtKind::Semi(self.mk_expr(lo.to(last_semi),
-                                ExprKind::Tup(Vec::new()),
-                                ThinVec::new()
-                            )),
-                        }));
-                    }
+            MacStmtStyle::NoBraces
+        };
 
-                    if self.token == token::CloseDelim(token::Brace) {
-                        unused_attrs(&attrs, self);
-                        return Ok(None);
-                    }
+        let mac = Mac {
+            path,
+            args,
+            prior_type_ascription: self.last_type_ascription,
+        };
 
-                    // Remainder are line-expr stmts.
-                    let e = self.parse_expr_res(
-                        Restrictions::STMT_EXPR, Some(attrs.into()))?;
-                    Stmt {
-                        id: DUMMY_NODE_ID,
-                        span: lo.to(e.span),
-                        kind: StmtKind::Expr(e),
-                    }
-                }
+        let kind = if delim == token::Brace
+            || self.token == token::Semi
+            || self.token == token::Eof
+        {
+            StmtKind::Mac(P((mac, style, attrs.into())))
+        }
+        // We used to incorrectly stop parsing macro-expanded statements here.
+        // If the next token will be an error anyway but could have parsed with the
+        // earlier behavior, stop parsing here and emit a warning to avoid breakage.
+        else if legacy_warnings
+            && self.token.can_begin_expr()
+            && match self.token.kind {
+                // These can continue an expression, so we can't stop parsing and warn.
+                token::OpenDelim(token::Paren) | token::OpenDelim(token::Bracket) |
+                token::BinOp(token::Minus) | token::BinOp(token::Star) |
+                token::BinOp(token::And) | token::BinOp(token::Or) |
+                token::AndAnd | token::OrOr |
+                token::DotDot | token::DotDotDot | token::DotDotEq => false,
+                _ => true,
             }
-        }))
+        {
+            self.warn_missing_semicolon();
+            StmtKind::Mac(P((mac, style, attrs)))
+        } else {
+            // Since none of the above applied, this is an expression statement macro.
+            let e = self.mk_expr(lo.to(hi), ExprKind::Mac(mac), AttrVec::new());
+            let e = self.maybe_recover_from_bad_qpath(e, true)?;
+            let e = self.parse_dot_or_call_expr_with(e, lo, attrs)?;
+            let e = self.parse_assoc_expr_with(0, LhsExpr::AlreadyParsed(e))?;
+            StmtKind::Expr(e)
+        };
+        Ok(Some(self.mk_stmt(lo.to(hi), kind)))
+    }
+
+    /// Error on outer attributes in this context.
+    /// Also error if the previous token was a doc comment.
+    fn error_outer_attrs(&self, attrs: &[Attribute]) {
+        if !attrs.is_empty() {
+            if self.prev_token_kind == PrevTokenKind::DocComment {
+                self.span_fatal_err(self.prev_span, Error::UselessDocComment).emit();
+            } else if attrs.iter().any(|a| a.style == AttrStyle::Outer) {
+                self.span_err(self.token.span, "expected statement after outer attribute");
+            }
+        }
+    }
+
+    fn is_kw_followed_by_ident(&self, kw: Symbol) -> bool {
+        self.token.is_keyword(kw)
+            && self.look_ahead(1, |t| t.is_ident() && !t.is_reserved_ident())
+    }
+
+    fn recover_stmt_local(
+        &mut self,
+        lo: Span,
+        attrs: AttrVec,
+        msg: &str,
+        sugg: &str,
+    ) -> PResult<'a, Option<Stmt>> {
+        let stmt = self.parse_local_mk(lo, attrs)?;
+        self.struct_span_err(lo, "invalid variable declaration")
+            .span_suggestion(lo, msg, sugg.to_string(), Applicability::MachineApplicable)
+            .emit();
+        Ok(Some(stmt))
+    }
+
+    fn parse_local_mk(&mut self, lo: Span, attrs: AttrVec) -> PResult<'a, Stmt> {
+        let local = self.parse_local(attrs.into())?;
+        Ok(self.mk_stmt(lo.to(self.prev_span), StmtKind::Local(local)))
     }
 
     /// Parses a local variable declaration.
-    fn parse_local(&mut self, attrs: ThinVec<Attribute>) -> PResult<'a, P<Local>> {
+    fn parse_local(&mut self, attrs: AttrVec) -> PResult<'a, P<Local>> {
         let lo = self.prev_span;
         let pat = self.parse_top_pat(GateOr::Yes)?;
 
@@ -307,70 +326,58 @@ impl<'a> Parser<'a> {
         let lo = self.token.span;
 
         if !self.eat(&token::OpenDelim(token::Brace)) {
-            let sp = self.token.span;
-            let tok = self.this_token_descr();
-            let mut e = self.span_fatal(sp, &format!("expected `{{`, found {}", tok));
-            let do_not_suggest_help =
-                self.token.is_keyword(kw::In) || self.token == token::Colon;
-
-            if self.token.is_ident_named(sym::and) {
-                e.span_suggestion_short(
-                    self.token.span,
-                    "use `&&` instead of `and` for the boolean operator",
-                    "&&".to_string(),
-                    Applicability::MaybeIncorrect,
-                );
-            }
-            if self.token.is_ident_named(sym::or) {
-                e.span_suggestion_short(
-                    self.token.span,
-                    "use `||` instead of `or` for the boolean operator",
-                    "||".to_string(),
-                    Applicability::MaybeIncorrect,
-                );
-            }
+            return self.error_block_no_opening_brace();
+        }
 
-            // Check to see if the user has written something like
-            //
-            //    if (cond)
-            //      bar;
-            //
-            // which is valid in other languages, but not Rust.
-            match self.parse_stmt_without_recovery(false) {
-                Ok(Some(stmt)) => {
-                    if self.look_ahead(1, |t| t == &token::OpenDelim(token::Brace))
-                        || do_not_suggest_help {
-                        // If the next token is an open brace (e.g., `if a b {`), the place-
-                        // inside-a-block suggestion would be more likely wrong than right.
-                        e.span_label(sp, "expected `{`");
-                        return Err(e);
-                    }
-                    let mut stmt_span = stmt.span;
-                    // Expand the span to include the semicolon, if it exists.
-                    if self.eat(&token::Semi) {
-                        stmt_span = stmt_span.with_hi(self.prev_span.hi());
-                    }
-                    if let Ok(snippet) = self.span_to_snippet(stmt_span) {
-                        e.span_suggestion(
-                            stmt_span,
-                            "try placing this code inside a block",
-                            format!("{{ {} }}", snippet),
-                            // Speculative; has been misleading in the past (#46836).
-                            Applicability::MaybeIncorrect,
-                        );
-                    }
+        self.parse_block_tail(lo, BlockCheckMode::Default)
+    }
+
+    fn error_block_no_opening_brace<T>(&mut self) -> PResult<'a, T> {
+        let sp = self.token.span;
+        let tok = self.this_token_descr();
+        let mut e = self.span_fatal(sp, &format!("expected `{{`, found {}", tok));
+        let do_not_suggest_help = self.token.is_keyword(kw::In) || self.token == token::Colon;
+
+        // Check to see if the user has written something like
+        //
+        //    if (cond)
+        //      bar;
+        //
+        // which is valid in other languages, but not Rust.
+        match self.parse_stmt_without_recovery(false) {
+            Ok(Some(stmt)) => {
+                if self.look_ahead(1, |t| t == &token::OpenDelim(token::Brace))
+                    || do_not_suggest_help
+                {
+                    // If the next token is an open brace (e.g., `if a b {`), the place-
+                    // inside-a-block suggestion would be more likely wrong than right.
+                    e.span_label(sp, "expected `{`");
+                    return Err(e);
                 }
-                Err(mut e) => {
-                    self.recover_stmt_(SemiColonMode::Break, BlockMode::Ignore);
-                    e.cancel();
+                let stmt_span = if self.eat(&token::Semi) {
+                    // Expand the span to include the semicolon.
+                    stmt.span.with_hi(self.prev_span.hi())
+                } else {
+                    stmt.span
+                };
+                if let Ok(snippet) = self.span_to_snippet(stmt_span) {
+                    e.span_suggestion(
+                        stmt_span,
+                        "try placing this code inside a block",
+                        format!("{{ {} }}", snippet),
+                        // Speculative; has been misleading in the past (#46836).
+                        Applicability::MaybeIncorrect,
+                    );
                 }
-                _ => ()
             }
-            e.span_label(sp, "expected `{`");
-            return Err(e);
+            Err(mut e) => {
+                self.recover_stmt_(SemiColonMode::Break, BlockMode::Ignore);
+                e.cancel();
+            }
+            _ => {}
         }
-
-        self.parse_block_tail(lo, BlockCheckMode::Default)
+        e.span_label(sp, "expected `{`");
+        return Err(e);
     }
 
     /// Parses a block. Inner attributes are allowed.
@@ -402,11 +409,10 @@ impl<'a> Parser<'a> {
                     self.maybe_annotate_with_ascription(&mut err, false);
                     err.emit();
                     self.recover_stmt_(SemiColonMode::Ignore, BlockMode::Ignore);
-                    Some(Stmt {
-                        id: DUMMY_NODE_ID,
-                        kind: StmtKind::Expr(self.mk_expr_err(self.token.span)),
-                        span: self.token.span,
-                    })
+                    Some(self.mk_stmt(
+                        self.token.span,
+                        StmtKind::Expr(self.mk_expr_err(self.token.span)),
+                    ))
                 }
                 Ok(stmt) => stmt,
             };
@@ -478,4 +484,8 @@ impl<'a> Parser<'a> {
             "this was erroneously allowed and will become a hard error in a future release"
         }).emit();
     }
+
+    fn mk_stmt(&self, span: Span, kind: StmtKind) -> Stmt {
+        Stmt { id: DUMMY_NODE_ID, kind, span }
+    }
 }
diff --git a/src/libsyntax/ast.rs b/src/libsyntax/ast.rs
index cf54fd2887a..c1458236788 100644
--- a/src/libsyntax/ast.rs
+++ b/src/libsyntax/ast.rs
@@ -338,7 +338,7 @@ pub enum GenericParamKind {
 pub struct GenericParam {
     pub id: NodeId,
     pub ident: Ident,
-    pub attrs: ThinVec<Attribute>,
+    pub attrs: AttrVec,
     pub bounds: GenericBounds,
     pub is_placeholder: bool,
     pub kind: GenericParamKind,
@@ -599,7 +599,7 @@ pub struct FieldPat {
     /// The pattern the field is destructured to
     pub pat: P<Pat>,
     pub is_shorthand: bool,
-    pub attrs: ThinVec<Attribute>,
+    pub attrs: AttrVec,
     pub id: NodeId,
     pub span: Span,
     pub is_placeholder: bool,
@@ -911,7 +911,7 @@ pub enum StmtKind {
     /// Expr with a trailing semi-colon.
     Semi(P<Expr>),
     /// Macro.
-    Mac(P<(Mac, MacStmtStyle, ThinVec<Attribute>)>),
+    Mac(P<(Mac, MacStmtStyle, AttrVec)>),
 }
 
 #[derive(Clone, Copy, PartialEq, RustcEncodable, RustcDecodable, Debug)]
@@ -936,7 +936,7 @@ pub struct Local {
     /// Initializer expression to set the value, if any.
     pub init: Option<P<Expr>>,
     pub span: Span,
-    pub attrs: ThinVec<Attribute>,
+    pub attrs: AttrVec,
 }
 
 /// An arm of a 'match'.
@@ -966,7 +966,7 @@ pub struct Arm {
 /// Access of a named (e.g., `obj.foo`) or unnamed (e.g., `obj.0`) struct field.
 #[derive(Clone, RustcEncodable, RustcDecodable, Debug)]
 pub struct Field {
-    pub attrs: ThinVec<Attribute>,
+    pub attrs: AttrVec,
     pub id: NodeId,
     pub span: Span,
     pub ident: Ident,
@@ -1004,7 +1004,7 @@ pub struct Expr {
     pub id: NodeId,
     pub kind: ExprKind,
     pub span: Span,
-    pub attrs: ThinVec<Attribute>,
+    pub attrs: AttrVec,
 }
 
 // `Expr` is used a lot. Make sure it doesn't unintentionally get bigger.
@@ -1961,7 +1961,7 @@ pub struct InlineAsm {
 /// E.g., `bar: usize` as in `fn foo(bar: usize)`.
 #[derive(Clone, RustcEncodable, RustcDecodable, Debug)]
 pub struct Param {
-    pub attrs: ThinVec<Attribute>,
+    pub attrs: AttrVec,
     pub ty: P<Ty>,
     pub pat: P<Pat>,
     pub id: NodeId,
@@ -2014,7 +2014,7 @@ impl Param {
     }
 
     /// Builds a `Param` object from `ExplicitSelf`.
-    pub fn from_self(attrs: ThinVec<Attribute>, eself: ExplicitSelf, eself_ident: Ident) -> Param {
+    pub fn from_self(attrs: AttrVec, eself: ExplicitSelf, eself_ident: Ident) -> Param {
         let span = eself.span.to(eself_ident.span);
         let infer_ty = P(Ty {
             id: DUMMY_NODE_ID,
@@ -2332,6 +2332,9 @@ pub struct AttrItem {
     pub args: MacArgs,
 }
 
+/// A list of attributes.
+pub type AttrVec = ThinVec<Attribute>;
+
 /// Metadata associated with an item.
 #[derive(Clone, RustcEncodable, RustcDecodable, Debug)]
 pub struct Attribute {
diff --git a/src/libsyntax/attr/mod.rs b/src/libsyntax/attr/mod.rs
index 13a9ed5f215..ae6d50ba083 100644
--- a/src/libsyntax/attr/mod.rs
+++ b/src/libsyntax/attr/mod.rs
@@ -9,7 +9,7 @@ pub use StabilityLevel::*;
 pub use crate::ast::Attribute;
 
 use crate::ast;
-use crate::ast::{AttrItem, AttrId, AttrKind, AttrStyle, Name, Ident, Path, PathSegment};
+use crate::ast::{AttrVec, AttrItem, AttrId, AttrKind, AttrStyle, Name, Ident, Path, PathSegment};
 use crate::ast::{MacArgs, MacDelimiter, MetaItem, MetaItemKind, NestedMetaItem};
 use crate::ast::{Lit, LitKind, Expr, Item, Local, Stmt, StmtKind, GenericParam};
 use crate::mut_visit::visit_clobber;
@@ -17,7 +17,6 @@ use crate::source_map::{BytePos, Spanned};
 use crate::token::{self, Token};
 use crate::ptr::P;
 use crate::symbol::{sym, Symbol};
-use crate::ThinVec;
 use crate::tokenstream::{DelimSpan, TokenStream, TokenTree, TreeAndJoint};
 use crate::GLOBALS;
 
@@ -665,7 +664,7 @@ impl HasAttrs for Vec<Attribute> {
     }
 }
 
-impl HasAttrs for ThinVec<Attribute> {
+impl HasAttrs for AttrVec {
     fn attrs(&self) -> &[Attribute] {
         self
     }
diff --git a/src/libsyntax/lib.rs b/src/libsyntax/lib.rs
index 36173801eae..30fd23ea909 100644
--- a/src/libsyntax/lib.rs
+++ b/src/libsyntax/lib.rs
@@ -24,7 +24,6 @@
 pub use errors;
 use rustc_data_structures::sync::Lock;
 use rustc_index::bit_set::GrowableBitSet;
-pub use rustc_data_structures::thin_vec::ThinVec;
 use ast::AttrId;
 use syntax_pos::edition::Edition;
 
diff --git a/src/libsyntax/mut_visit.rs b/src/libsyntax/mut_visit.rs
index 2a6cff5971c..1d27f70f5a5 100644
--- a/src/libsyntax/mut_visit.rs
+++ b/src/libsyntax/mut_visit.rs
@@ -11,7 +11,6 @@ use crate::ast::*;
 use crate::source_map::{Spanned, respan};
 use crate::token::{self, Token};
 use crate::ptr::P;
-use crate::ThinVec;
 use crate::tokenstream::*;
 use crate::util::map_in_place::MapInPlace;
 
@@ -337,7 +336,7 @@ pub fn visit_attrs<T: MutVisitor>(attrs: &mut Vec<Attribute>, vis: &mut T) {
 }
 
 // No `noop_` prefix because there isn't a corresponding method in `MutVisitor`.
-pub fn visit_thin_attrs<T: MutVisitor>(attrs: &mut ThinVec<Attribute>, vis: &mut T) {
+pub fn visit_thin_attrs<T: MutVisitor>(attrs: &mut AttrVec, vis: &mut T) {
     for attr in attrs.iter_mut() {
         vis.visit_attribute(attr);
     }
diff --git a/src/libsyntax_expand/base.rs b/src/libsyntax_expand/base.rs
index 75066a006bf..2ad327e872e 100644
--- a/src/libsyntax_expand/base.rs
+++ b/src/libsyntax_expand/base.rs
@@ -9,7 +9,6 @@ use syntax::mut_visit::{self, MutVisitor};
 use syntax::ptr::P;
 use syntax::sess::ParseSess;
 use syntax::symbol::{kw, sym, Ident, Symbol};
-use syntax::ThinVec;
 use syntax::token;
 use syntax::tokenstream::{self, TokenStream};
 use syntax::visit::Visitor;
@@ -552,7 +551,7 @@ impl DummyResult {
             id: ast::DUMMY_NODE_ID,
             kind: if is_error { ast::ExprKind::Err } else { ast::ExprKind::Tup(Vec::new()) },
             span: sp,
-            attrs: ThinVec::new(),
+            attrs: ast::AttrVec::new(),
         })
     }
 
diff --git a/src/libsyntax_expand/build.rs b/src/libsyntax_expand/build.rs
index 3d082101c41..4c539cad111 100644
--- a/src/libsyntax_expand/build.rs
+++ b/src/libsyntax_expand/build.rs
@@ -1,11 +1,10 @@
 use crate::base::ExtCtxt;
 
-use syntax::ast::{self, Ident, Expr, BlockCheckMode, UnOp, PatKind};
+use syntax::ast::{self, AttrVec, Ident, Expr, BlockCheckMode, UnOp, PatKind};
 use syntax::attr;
 use syntax::source_map::{respan, Spanned};
 use syntax::ptr::P;
 use syntax::symbol::{kw, sym, Symbol};
-use syntax::ThinVec;
 
 use syntax_pos::{Pos, Span};
 
@@ -81,7 +80,7 @@ impl<'a> ExtCtxt<'a> {
                 id: ast::DUMMY_NODE_ID,
                 kind,
                 span,
-                attrs: ThinVec::new(),
+                attrs: AttrVec::new(),
             })
         }
     }
@@ -190,7 +189,7 @@ impl<'a> ExtCtxt<'a> {
             init: Some(ex),
             id: ast::DUMMY_NODE_ID,
             span: sp,
-            attrs: ThinVec::new(),
+            attrs: AttrVec::new(),
         });
         ast::Stmt {
             id: ast::DUMMY_NODE_ID,
@@ -207,7 +206,7 @@ impl<'a> ExtCtxt<'a> {
             init: None,
             id: ast::DUMMY_NODE_ID,
             span,
-            attrs: ThinVec::new(),
+            attrs: AttrVec::new(),
         });
         ast::Stmt {
             id: ast::DUMMY_NODE_ID,
@@ -245,7 +244,7 @@ impl<'a> ExtCtxt<'a> {
             id: ast::DUMMY_NODE_ID,
             kind,
             span,
-            attrs: ThinVec::new(),
+            attrs: AttrVec::new(),
         })
     }
 
@@ -304,7 +303,7 @@ impl<'a> ExtCtxt<'a> {
             expr: e,
             span,
             is_shorthand: false,
-            attrs: ThinVec::new(),
+            attrs: AttrVec::new(),
             id: ast::DUMMY_NODE_ID,
             is_placeholder: false,
         }
@@ -549,7 +548,7 @@ impl<'a> ExtCtxt<'a> {
     pub fn param(&self, span: Span, ident: ast::Ident, ty: P<ast::Ty>) -> ast::Param {
         let arg_pat = self.pat_ident(span, ident);
         ast::Param {
-            attrs: ThinVec::default(),
+            attrs: AttrVec::default(),
             id: ast::DUMMY_NODE_ID,
             pat: arg_pat,
             span,
diff --git a/src/libsyntax_expand/mbe/macro_rules.rs b/src/libsyntax_expand/mbe/macro_rules.rs
index 2dd15872a9f..107fe388ed0 100644
--- a/src/libsyntax_expand/mbe/macro_rules.rs
+++ b/src/libsyntax_expand/mbe/macro_rules.rs
@@ -63,6 +63,30 @@ crate fn annotate_err_with_kind(
     };
 }
 
+/// Instead of e.g. `vec![a, b, c]` in a pattern context, suggest `[a, b, c]`.
+fn suggest_slice_pat(e: &mut DiagnosticBuilder<'_>, site_span: Span, parser: &Parser<'_>) {
+    let mut suggestion = None;
+    if let Ok(code) = parser.sess.source_map().span_to_snippet(site_span) {
+        if let Some(bang) = code.find('!') {
+            suggestion = Some(code[bang + 1..].to_string());
+        }
+    }
+    if let Some(suggestion) = suggestion {
+        e.span_suggestion(
+            site_span,
+            "use a slice pattern here instead",
+            suggestion,
+            Applicability::MachineApplicable,
+        );
+    } else {
+        e.span_label(site_span, "use a slice pattern here instead");
+    }
+    e.help(
+        "for more information, see https://doc.rust-lang.org/edition-guide/\
+        rust-2018/slice-patterns.html"
+    );
+}
+
 impl<'a> ParserAnyMacro<'a> {
     crate fn make(mut self: Box<ParserAnyMacro<'a>>, kind: AstFragmentKind) -> AstFragment {
         let ParserAnyMacro { site_span, macro_ident, ref mut parser, arm_span } = *self;
@@ -92,27 +116,7 @@ impl<'a> ParserAnyMacro<'a> {
             }
             match kind {
                 AstFragmentKind::Pat if macro_ident.name == sym::vec => {
-                    let mut suggestion = None;
-                    if let Ok(code) = parser.sess.source_map().span_to_snippet(site_span) {
-                        if let Some(bang) = code.find('!') {
-                            suggestion = Some(code[bang + 1..].to_string());
-                        }
-                    }
-                    if let Some(suggestion) = suggestion {
-                        e.span_suggestion(
-                            site_span,
-                            "use a slice pattern here instead",
-                            suggestion,
-                            Applicability::MachineApplicable,
-                        );
-                    } else {
-                        e.span_label(
-                            site_span,
-                            "use a slice pattern here instead",
-                        );
-                    }
-                    e.help("for more information, see https://doc.rust-lang.org/edition-guide/\
-                            rust-2018/slice-patterns.html");
+                    suggest_slice_pat(&mut e, site_span, parser);
                 }
                 _ => annotate_err_with_kind(&mut e, kind, site_span),
             };
diff --git a/src/libsyntax_expand/placeholders.rs b/src/libsyntax_expand/placeholders.rs
index 22e99baae5b..4298e0e74b6 100644
--- a/src/libsyntax_expand/placeholders.rs
+++ b/src/libsyntax_expand/placeholders.rs
@@ -5,7 +5,6 @@ use syntax::ast;
 use syntax::source_map::{DUMMY_SP, dummy_spanned};
 use syntax::mut_visit::*;
 use syntax::ptr::P;
-use syntax::ThinVec;
 
 use smallvec::{smallvec, SmallVec};
 
@@ -28,7 +27,7 @@ pub fn placeholder(kind: AstFragmentKind, id: ast::NodeId, vis: Option<ast::Visi
     let span = DUMMY_SP;
     let expr_placeholder = || P(ast::Expr {
         id, span,
-        attrs: ThinVec::new(),
+        attrs: ast::AttrVec::new(),
         kind: ast::ExprKind::Mac(mac_placeholder()),
     });
     let ty = || P(ast::Ty {
@@ -75,7 +74,7 @@ pub fn placeholder(kind: AstFragmentKind, id: ast::NodeId, vis: Option<ast::Visi
             id, span, kind: ast::TyKind::Mac(mac_placeholder()),
         })),
         AstFragmentKind::Stmts => AstFragment::Stmts(smallvec![{
-            let mac = P((mac_placeholder(), ast::MacStmtStyle::Braces, ThinVec::new()));
+            let mac = P((mac_placeholder(), ast::MacStmtStyle::Braces, ast::AttrVec::new()));
             ast::Stmt { id, span, kind: ast::StmtKind::Mac(mac) }
         }]),
         AstFragmentKind::Arms => AstFragment::Arms(smallvec![
diff --git a/src/libsyntax_ext/asm.rs b/src/libsyntax_ext/asm.rs
index bd345a9a7da..5ec24b7a7ac 100644
--- a/src/libsyntax_ext/asm.rs
+++ b/src/libsyntax_ext/asm.rs
@@ -3,7 +3,6 @@
 use State::*;
 
 use errors::{DiagnosticBuilder, PResult};
-use rustc_data_structures::thin_vec::ThinVec;
 use rustc_parse::parser::Parser;
 use syntax_expand::base::*;
 use syntax_pos::Span;
@@ -63,7 +62,7 @@ pub fn expand_asm<'cx>(cx: &'cx mut ExtCtxt<'_>,
         id: ast::DUMMY_NODE_ID,
         kind: ast::ExprKind::InlineAsm(P(inline_asm)),
         span: cx.with_def_site_ctxt(sp),
-        attrs: ThinVec::new(),
+        attrs: ast::AttrVec::new(),
     }))
 }
 
diff --git a/src/libsyntax_ext/concat_idents.rs b/src/libsyntax_ext/concat_idents.rs
index 8a1bc56cf1c..7d8bc8b87bc 100644
--- a/src/libsyntax_ext/concat_idents.rs
+++ b/src/libsyntax_ext/concat_idents.rs
@@ -1,5 +1,3 @@
-use rustc_data_structures::thin_vec::ThinVec;
-
 use syntax::ast;
 use syntax_expand::base::{self, *};
 use syntax::token::{self, Token};
@@ -49,7 +47,7 @@ pub fn expand_concat_idents<'cx>(cx: &'cx mut ExtCtxt<'_>,
                 id: ast::DUMMY_NODE_ID,
                 kind: ast::ExprKind::Path(None, ast::Path::from_ident(self.ident)),
                 span: self.ident.span,
-                attrs: ThinVec::new(),
+                attrs: ast::AttrVec::new(),
             }))
         }
 
diff --git a/src/libsyntax_ext/deriving/debug.rs b/src/libsyntax_ext/deriving/debug.rs
index 35298211e4d..132ce76e1bb 100644
--- a/src/libsyntax_ext/deriving/debug.rs
+++ b/src/libsyntax_ext/deriving/debug.rs
@@ -2,8 +2,6 @@ use crate::deriving::path_std;
 use crate::deriving::generic::*;
 use crate::deriving::generic::ty::*;
 
-use rustc_data_structures::thin_vec::ThinVec;
-
 use syntax::ast::{self, Ident};
 use syntax::ast::{Expr, MetaItem};
 use syntax_expand::base::{Annotatable, ExtCtxt};
@@ -127,7 +125,7 @@ fn stmt_let_undescore(cx: &mut ExtCtxt<'_>, sp: Span, expr: P<ast::Expr>) -> ast
         init: Some(expr),
         id: ast::DUMMY_NODE_ID,
         span: sp,
-        attrs: ThinVec::new(),
+        attrs: ast::AttrVec::new(),
     });
     ast::Stmt {
         id: ast::DUMMY_NODE_ID,
diff --git a/src/libsyntax_ext/deriving/generic/mod.rs b/src/libsyntax_ext/deriving/generic/mod.rs
index b7707bfb8e5..5158a5b3da2 100644
--- a/src/libsyntax_ext/deriving/generic/mod.rs
+++ b/src/libsyntax_ext/deriving/generic/mod.rs
@@ -181,7 +181,6 @@ use std::cell::RefCell;
 use std::iter;
 use std::vec;
 
-use rustc_data_structures::thin_vec::ThinVec;
 use syntax::ast::{self, BinOpKind, EnumDef, Expr, Generics, Ident, PatKind};
 use syntax::ast::{VariantData, GenericParamKind, GenericArg};
 use syntax::attr;
@@ -919,7 +918,7 @@ impl<'a> MethodDef<'a> {
         let args = {
             let self_args = explicit_self.map(|explicit_self| {
                 let ident = Ident::with_dummy_span(kw::SelfLower).with_span_pos(trait_.span);
-                ast::Param::from_self(ThinVec::default(), explicit_self, ident)
+                ast::Param::from_self(ast::AttrVec::default(), explicit_self, ident)
             });
             let nonself_args = arg_types.into_iter()
                 .map(|(name, ty)| cx.param(trait_.span, name, ty));
@@ -1608,7 +1607,7 @@ impl<'a> TraitDef<'a> {
                         ast::FieldPat {
                             ident: ident.unwrap(),
                             is_shorthand: false,
-                            attrs: ThinVec::new(),
+                            attrs: ast::AttrVec::new(),
                             id: ast::DUMMY_NODE_ID,
                             span: pat.span.with_ctxt(self.span.ctxt()),
                             pat,
diff --git a/src/libsyntax_pos/symbol.rs b/src/libsyntax_pos/symbol.rs
index ae34064c926..d3e80fc4fdd 100644
--- a/src/libsyntax_pos/symbol.rs
+++ b/src/libsyntax_pos/symbol.rs
@@ -773,6 +773,7 @@ symbols! {
         usize,
         v1,
         val,
+        var,
         vec,
         Vec,
         vis,
diff --git a/src/test/pretty/ast-stmt-expr-attr.rs b/src/test/pretty/ast-stmt-expr-attr.rs
new file mode 100644
index 00000000000..5b975424512
--- /dev/null
+++ b/src/test/pretty/ast-stmt-expr-attr.rs
@@ -0,0 +1,175 @@
+// pp-exact
+
+fn main() { }
+
+#[cfg(FALSE)]
+fn syntax() {
+    let _ = #[attr] box 0;
+    let _ = #[attr] [#![attr] ];
+    let _ = #[attr] [#![attr] 0];
+    let _ = #[attr] [#![attr] 0; 0];
+    let _ = #[attr] [#![attr] 0, 0, 0];
+    let _ = #[attr] foo();
+    let _ = #[attr] x.foo();
+    let _ = #[attr] (#![attr] );
+    let _ = #[attr] (#![attr] #[attr] 0,);
+    let _ = #[attr] (#![attr] #[attr] 0, 0);
+    let _ = #[attr] 0 + #[attr] 0;
+    let _ = #[attr] 0 / #[attr] 0;
+    let _ = #[attr] 0 & #[attr] 0;
+    let _ = #[attr] 0 % #[attr] 0;
+    let _ = #[attr] (0 + 0);
+    let _ = #[attr] !0;
+    let _ = #[attr] -0;
+    let _ = #[attr] false;
+    let _ = #[attr] 0;
+    let _ = #[attr] 'c';
+    let _ = #[attr] x as Y;
+    let _ = #[attr] (x as Y);
+    let _ =
+        #[attr] while true {
+                    #![attr]
+                };
+    let _ =
+        #[attr] while let Some(false) = true {
+                    #![attr]
+                };
+    let _ =
+        #[attr] for x in y {
+                    #![attr]
+                };
+    let _ =
+        #[attr] loop  {
+                    #![attr]
+                };
+    let _ =
+        #[attr] match true {
+                    #![attr]
+                            #[attr]
+                            _ => false,
+                };
+    let _ = #[attr] || #[attr] foo;
+    let _ = #[attr] move || #[attr] foo;
+    let _ =
+        #[attr] ||
+                    #[attr] {
+                                #![attr]
+                                foo
+                            };
+    let _ =
+        #[attr] move ||
+                    #[attr] {
+                                #![attr]
+                                foo
+                            };
+    let _ =
+        #[attr] ||
+                    {
+                        #![attr]
+                        foo
+                    };
+    let _ =
+        #[attr] move ||
+                    {
+                        #![attr]
+                        foo
+                    };
+    let _ =
+        #[attr] {
+                    #![attr]
+                };
+    let _ =
+        #[attr] {
+                    #![attr]
+                    let _ = ();
+                };
+    let _ =
+        #[attr] {
+                    #![attr]
+                    let _ = ();
+                    foo
+                };
+    let _ = #[attr] x = y;
+    let _ = #[attr] (x = y);
+    let _ = #[attr] x += y;
+    let _ = #[attr] (x += y);
+    let _ = #[attr] foo.bar;
+    let _ = (#[attr] foo).bar;
+    let _ = #[attr] foo.0;
+    let _ = (#[attr] foo).0;
+    let _ = #[attr] foo[bar];
+    let _ = (#[attr] foo)[bar];
+    let _ = #[attr] 0..#[attr] 0;
+    let _ = #[attr] 0..;
+    let _ = #[attr] (0..0);
+    let _ = #[attr] (0..);
+    let _ = #[attr] (..0);
+    let _ = #[attr] (..);
+    let _ = #[attr] foo::bar::baz;
+    let _ = #[attr] &0;
+    let _ = #[attr] &mut 0;
+    let _ = #[attr] &#[attr] 0;
+    let _ = #[attr] &mut #[attr] 0;
+    let _ = #[attr] break ;
+    let _ = #[attr] continue ;
+    let _ = #[attr] return;
+    let _ = #[attr] foo!();
+    let _ = #[attr] foo!(# ! [attr]);
+    let _ = #[attr] foo![];
+    let _ = #[attr] foo![# ! [attr]];
+    let _ = #[attr] foo! { };
+    let _ = #[attr] foo! { # ! [attr] };
+    let _ = #[attr] Foo{#![attr] bar: baz,};
+    let _ = #[attr] Foo{#![attr] ..foo};
+    let _ = #[attr] Foo{#![attr] bar: baz, ..foo};
+    let _ = #[attr] (#![attr] 0);
+
+    {
+        #[attr]
+        let _ = 0;
+
+        #[attr]
+        0;
+
+        #[attr]
+        foo!();
+
+        #[attr]
+        foo! { }
+
+        #[attr]
+        foo![];
+    }
+
+    {
+        #[attr]
+        let _ = 0;
+    }
+    {
+
+        #[attr]
+        0
+    }
+    {
+
+        #[attr]
+        {
+            #![attr]
+        }
+    }
+    {
+
+        #[attr]
+        foo!()
+    }
+    {
+
+        #[attr]
+        foo![]
+    }
+    {
+
+        #[attr]
+        foo! { }
+    }
+}
diff --git a/src/test/ui-fulldeps/ast_stmt_expr_attr.rs b/src/test/ui-fulldeps/ast_stmt_expr_attr.rs
deleted file mode 100644
index d6d49df63ef..00000000000
--- a/src/test/ui-fulldeps/ast_stmt_expr_attr.rs
+++ /dev/null
@@ -1,311 +0,0 @@
-// run-pass
-
-#![allow(unused_imports)]
-// ignore-cross-compile
-
-#![feature(rustc_private)]
-
-extern crate syntax;
-extern crate syntax_expand;
-extern crate rustc_parse;
-extern crate rustc_errors;
-
-use rustc_errors::PResult;
-use rustc_parse::parser::attr::*;
-use rustc_parse::new_parser_from_source_str;
-use rustc_parse::parser::Parser;
-use syntax::ast::*;
-use syntax::attr::*;
-use syntax::ast;
-use syntax::sess::ParseSess;
-use syntax::source_map::{FilePathMapping, FileName};
-use syntax::ptr::P;
-use syntax::print::pprust;
-use syntax::token;
-use std::fmt;
-
-// Copied out of syntax::util::parser_testing
-
-pub fn string_to_parser<'a>(ps: &'a ParseSess, source_str: String) -> Parser<'a> {
-    new_parser_from_source_str(ps, FileName::Custom(source_str.clone()), source_str)
-}
-
-fn with_error_checking_parse<'a, T, F>(s: String, ps: &'a ParseSess, f: F) -> PResult<'a, T> where
-    F: FnOnce(&mut Parser<'a>) -> PResult<'a, T>,
-{
-    let mut p = string_to_parser(&ps, s);
-    let x = f(&mut p);
-
-    if ps.span_diagnostic.has_errors() || p.token != token::Eof {
-        if let Err(mut e) = x {
-            e.cancel();
-        }
-        return Err(p.fatal("parse error"));
-    }
-
-    x
-}
-
-fn expr<'a>(s: &str, ps: &'a ParseSess) -> PResult<'a, P<ast::Expr>> {
-    with_error_checking_parse(s.to_string(), ps, |p| {
-        p.parse_expr()
-    })
-}
-
-fn stmt<'a>(s: &str, ps: &'a ParseSess) -> PResult<'a, ast::Stmt> {
-    with_error_checking_parse(s.to_string(), ps, |p| {
-        p.parse_stmt().map(|s| s.unwrap())
-    })
-}
-
-fn attr<'a>(s: &str, ps: &'a ParseSess) -> PResult<'a, ast::Attribute> {
-    with_error_checking_parse(s.to_string(), ps, |p| {
-        p.parse_attribute(true)
-    })
-}
-
-fn str_compare<T, F: Fn(&T) -> String>(e: &str, expected: &[T], actual: &[T], f: F) {
-    let expected: Vec<_> = expected.iter().map(|e| f(e)).collect();
-    let actual: Vec<_> = actual.iter().map(|e| f(e)).collect();
-
-    if expected != actual {
-        panic!("parsed `{}` as {:?}, expected {:?}", e, actual, expected);
-    }
-}
-
-fn sess() -> ParseSess {
-    ParseSess::new(FilePathMapping::empty())
-}
-
-fn check_expr_attrs(es: &str, expected: &[&str]) {
-    let ps = sess();
-    let e = expr(es, &ps).expect("parse error");
-    let actual = &e.attrs;
-    str_compare(es,
-                &expected.iter().map(|r| attr(r, &ps).unwrap()).collect::<Vec<_>>(),
-                &actual,
-                pprust::attribute_to_string);
-}
-
-fn check_stmt_attrs(es: &str, expected: &[&str]) {
-    let ps = sess();
-    let e = stmt(es, &ps).expect("parse error");
-    let actual = e.kind.attrs();
-    str_compare(es,
-                &expected.iter().map(|r| attr(r, &ps).unwrap()).collect::<Vec<_>>(),
-                actual,
-                pprust::attribute_to_string);
-}
-
-fn reject_expr_parse(es: &str) {
-    let ps = sess();
-    match expr(es, &ps) {
-        Ok(_) => panic!("parser did not reject `{}`", es),
-        Err(mut e) => e.cancel(),
-    };
-}
-
-fn reject_stmt_parse(es: &str) {
-    let ps = sess();
-    match stmt(es, &ps) {
-        Ok(_) => panic!("parser did not reject `{}`", es),
-        Err(mut e) => e.cancel(),
-    };
-}
-
-fn main() {
-    syntax::with_default_globals(|| run());
-}
-
-fn run() {
-    let both = &["#[attr]", "#![attr]"];
-    let outer = &["#[attr]"];
-    let none = &[];
-
-    check_expr_attrs("#[attr] box 0", outer);
-    reject_expr_parse("box #![attr] 0");
-
-    check_expr_attrs("#[attr] [#![attr]]", both);
-    check_expr_attrs("#[attr] [#![attr] 0]", both);
-    check_expr_attrs("#[attr] [#![attr] 0; 0]", both);
-    check_expr_attrs("#[attr] [#![attr] 0, 0, 0]", both);
-    reject_expr_parse("[#[attr]]");
-
-    check_expr_attrs("#[attr] foo()", outer);
-    check_expr_attrs("#[attr] x.foo()", outer);
-    reject_expr_parse("foo#[attr]()");
-    reject_expr_parse("foo(#![attr])");
-    reject_expr_parse("x.foo(#![attr])");
-    reject_expr_parse("x.#[attr]foo()");
-    reject_expr_parse("x.#![attr]foo()");
-
-    check_expr_attrs("#[attr] (#![attr])", both);
-    check_expr_attrs("#[attr] (#![attr] #[attr] 0,)", both);
-    check_expr_attrs("#[attr] (#![attr] #[attr] 0, 0)", both);
-
-    check_expr_attrs("#[attr] 0 + #[attr] 0", none);
-    check_expr_attrs("#[attr] 0 / #[attr] 0", none);
-    check_expr_attrs("#[attr] 0 & #[attr] 0", none);
-    check_expr_attrs("#[attr] 0 % #[attr] 0", none);
-    check_expr_attrs("#[attr] (0 + 0)", outer);
-    reject_expr_parse("0 + #![attr] 0");
-
-    check_expr_attrs("#[attr] !0", outer);
-    check_expr_attrs("#[attr] -0", outer);
-    reject_expr_parse("!#![attr] 0");
-    reject_expr_parse("-#![attr] 0");
-
-    check_expr_attrs("#[attr] false", outer);
-    check_expr_attrs("#[attr] 0", outer);
-    check_expr_attrs("#[attr] 'c'", outer);
-
-    check_expr_attrs("#[attr] x as Y", none);
-    check_expr_attrs("#[attr] (x as Y)", outer);
-    reject_expr_parse("x #![attr] as Y");
-
-    reject_expr_parse("#[attr] if false {}");
-    reject_expr_parse("if false #[attr] {}");
-    reject_expr_parse("if false {#![attr]}");
-    reject_expr_parse("if false {} #[attr] else {}");
-    reject_expr_parse("if false {} else #[attr] {}");
-    reject_expr_parse("if false {} else {#![attr]}");
-    reject_expr_parse("if false {} else #[attr] if true {}");
-    reject_expr_parse("if false {} else if true #[attr] {}");
-    reject_expr_parse("if false {} else if true {#![attr]}");
-
-    reject_expr_parse("#[attr] if let Some(false) = false {}");
-    reject_expr_parse("if let Some(false) = false #[attr] {}");
-    reject_expr_parse("if let Some(false) = false {#![attr]}");
-    reject_expr_parse("if let Some(false) = false {} #[attr] else {}");
-    reject_expr_parse("if let Some(false) = false {} else #[attr] {}");
-    reject_expr_parse("if let Some(false) = false {} else {#![attr]}");
-    reject_expr_parse("if let Some(false) = false {} else #[attr] if let Some(false) = true {}");
-    reject_expr_parse("if let Some(false) = false {} else if let Some(false) = true #[attr] {}");
-    reject_expr_parse("if let Some(false) = false {} else if let Some(false) = true {#![attr]}");
-
-    check_expr_attrs("#[attr] while true {#![attr]}", both);
-
-    check_expr_attrs("#[attr] while let Some(false) = true {#![attr]}", both);
-
-    check_expr_attrs("#[attr] for x in y {#![attr]}", both);
-
-    check_expr_attrs("#[attr] loop {#![attr]}", both);
-
-    check_expr_attrs("#[attr] match true {#![attr] #[attr] _ => false}", both);
-
-    check_expr_attrs("#[attr]      || #[attr] foo", outer);
-    check_expr_attrs("#[attr] move || #[attr] foo", outer);
-    check_expr_attrs("#[attr]      || #[attr] { #![attr] foo }", outer);
-    check_expr_attrs("#[attr] move || #[attr] { #![attr] foo }", outer);
-    check_expr_attrs("#[attr]      || { #![attr] foo }", outer);
-    check_expr_attrs("#[attr] move || { #![attr] foo }", outer);
-    reject_expr_parse("|| #![attr] foo");
-    reject_expr_parse("move || #![attr] foo");
-    reject_expr_parse("|| #![attr] {foo}");
-    reject_expr_parse("move || #![attr] {foo}");
-
-    check_expr_attrs("#[attr] { #![attr] }", both);
-    check_expr_attrs("#[attr] { #![attr] let _ = (); }", both);
-    check_expr_attrs("#[attr] { #![attr] let _ = (); foo }", both);
-
-    check_expr_attrs("#[attr] x = y", none);
-    check_expr_attrs("#[attr] (x = y)", outer);
-
-    check_expr_attrs("#[attr] x += y", none);
-    check_expr_attrs("#[attr] (x += y)", outer);
-
-    check_expr_attrs("#[attr] foo.bar", outer);
-    check_expr_attrs("(#[attr] foo).bar", none);
-
-    check_expr_attrs("#[attr] foo.0", outer);
-    check_expr_attrs("(#[attr] foo).0", none);
-
-    check_expr_attrs("#[attr] foo[bar]", outer);
-    check_expr_attrs("(#[attr] foo)[bar]", none);
-
-    check_expr_attrs("#[attr] 0..#[attr] 0", none);
-    check_expr_attrs("#[attr] 0..", none);
-    reject_expr_parse("#[attr] ..#[attr] 0");
-    reject_expr_parse("#[attr] ..");
-
-    check_expr_attrs("#[attr] (0..0)", outer);
-    check_expr_attrs("#[attr] (0..)", outer);
-    check_expr_attrs("#[attr] (..0)", outer);
-    check_expr_attrs("#[attr] (..)", outer);
-
-    check_expr_attrs("#[attr] foo::bar::baz", outer);
-
-    check_expr_attrs("#[attr] &0", outer);
-    check_expr_attrs("#[attr] &mut 0", outer);
-    check_expr_attrs("#[attr] & #[attr] 0", outer);
-    check_expr_attrs("#[attr] &mut #[attr] 0", outer);
-    reject_expr_parse("#[attr] &#![attr] 0");
-    reject_expr_parse("#[attr] &mut #![attr] 0");
-
-    check_expr_attrs("#[attr] break", outer);
-    check_expr_attrs("#[attr] continue", outer);
-    check_expr_attrs("#[attr] return", outer);
-
-    check_expr_attrs("#[attr] foo!()", outer);
-    check_expr_attrs("#[attr] foo!(#![attr])", outer);
-    check_expr_attrs("#[attr] foo![]", outer);
-    check_expr_attrs("#[attr] foo![#![attr]]", outer);
-    check_expr_attrs("#[attr] foo!{}", outer);
-    check_expr_attrs("#[attr] foo!{#![attr]}", outer);
-
-    check_expr_attrs("#[attr] Foo { #![attr] bar: baz }", both);
-    check_expr_attrs("#[attr] Foo { #![attr] ..foo }", both);
-    check_expr_attrs("#[attr] Foo { #![attr] bar: baz, ..foo }", both);
-
-    check_expr_attrs("#[attr] (#![attr] 0)", both);
-
-    // Look at statements in their natural habitat...
-    check_expr_attrs("{
-        #[attr] let _ = 0;
-        #[attr] 0;
-        #[attr] foo!();
-        #[attr] foo!{}
-        #[attr] foo![];
-    }", none);
-
-    check_stmt_attrs("#[attr] let _ = 0", outer);
-    check_stmt_attrs("#[attr] 0",         outer);
-    check_stmt_attrs("#[attr] {#![attr]}", both);
-    check_stmt_attrs("#[attr] foo!()",    outer);
-    check_stmt_attrs("#[attr] foo![]",    outer);
-    check_stmt_attrs("#[attr] foo!{}",    outer);
-
-    reject_stmt_parse("#[attr] #![attr] let _ = 0");
-    reject_stmt_parse("#[attr] #![attr] 0");
-    reject_stmt_parse("#[attr] #![attr] foo!()");
-    reject_stmt_parse("#[attr] #![attr] foo![]");
-    reject_stmt_parse("#[attr] #![attr] foo!{}");
-
-    // FIXME: Allow attributes in pattern constexprs?
-    // note: requires parens in patterns to allow disambiguation
-
-    reject_expr_parse("match 0 {
-        0..=#[attr] 10 => ()
-    }");
-    reject_expr_parse("match 0 {
-        0..=#[attr] -10 => ()
-    }");
-    reject_expr_parse("match 0 {
-        0..=-#[attr] 10 => ()
-    }");
-    reject_expr_parse("match 0 {
-        0..=#[attr] FOO => ()
-    }");
-
-    // make sure we don't catch this bug again...
-    reject_expr_parse("{
-        fn foo() {
-            #[attr];
-        }
-    }");
-    reject_expr_parse("{
-        fn foo() {
-            #[attr]
-        }
-    }");
-}
diff --git a/src/test/ui/did_you_mean/issue-54109-and_instead_of_ampersands.rs b/src/test/ui/did_you_mean/issue-54109-and_instead_of_ampersands.rs
index 687479bad3f..44421b077fa 100644
--- a/src/test/ui/did_you_mean/issue-54109-and_instead_of_ampersands.rs
+++ b/src/test/ui/did_you_mean/issue-54109-and_instead_of_ampersands.rs
@@ -1,17 +1,25 @@
+fn main() {}
+
 fn test_and() {
     let a = true;
     let b = false;
-    if a and b {
-        //~^ ERROR expected `{`, found `and`
+
+    let _ = a and b; //~ ERROR `and` is not a logical operator
+
+    if a and b { //~ ERROR `and` is not a logical operator
         println!("both");
     }
+
+    let _recovery_witness: () = 0; //~ ERROR mismatched types
 }
 
 fn test_or() {
     let a = true;
     let b = false;
-    if a or b {
-        //~^ ERROR expected `{`, found `or`
+
+    let _ = a or b; //~ ERROR `or` is not a logical operator
+
+    if a or b { //~ ERROR `or` is not a logical operator
         println!("both");
     }
 }
@@ -19,8 +27,7 @@ fn test_or() {
 fn test_and_par() {
     let a = true;
     let b = false;
-    if (a and b) {
-        //~^ ERROR expected one of `!`, `)`, `,`, `.`, `::`, `?`, `{`, or an operator, found `and`
+    if (a and b) {  //~ ERROR `and` is not a logical operator
         println!("both");
     }
 }
@@ -28,8 +35,7 @@ fn test_and_par() {
 fn test_or_par() {
     let a = true;
     let b = false;
-    if (a or b) {
-        //~^ ERROR expected one of `!`, `)`, `,`, `.`, `::`, `?`, `{`, or an operator, found `or`
+    if (a or b) {  //~ ERROR `or` is not a logical operator
         println!("both");
     }
 }
@@ -37,8 +43,7 @@ fn test_or_par() {
 fn test_while_and() {
     let a = true;
     let b = false;
-    while a and b {
-        //~^ ERROR expected one of `!`, `.`, `::`, `?`, `{`, or an operator, found `and`
+    while a and b {  //~ ERROR `and` is not a logical operator
         println!("both");
     }
 }
@@ -46,11 +51,7 @@ fn test_while_and() {
 fn test_while_or() {
     let a = true;
     let b = false;
-    while a or b {
-        //~^ ERROR expected one of `!`, `.`, `::`, `?`, `{`, or an operator, found `or`
+    while a or b { //~ ERROR `or` is not a logical operator
         println!("both");
     }
 }
-
-fn main() {
-}
diff --git a/src/test/ui/did_you_mean/issue-54109-and_instead_of_ampersands.stderr b/src/test/ui/did_you_mean/issue-54109-and_instead_of_ampersands.stderr
index f230395f7a5..528c62f501e 100644
--- a/src/test/ui/did_you_mean/issue-54109-and_instead_of_ampersands.stderr
+++ b/src/test/ui/did_you_mean/issue-54109-and_instead_of_ampersands.stderr
@@ -1,58 +1,75 @@
-error: expected `{`, found `and`
-  --> $DIR/issue-54109-and_instead_of_ampersands.rs:4:10
+error: `and` is not a logical operator
+  --> $DIR/issue-54109-and_instead_of_ampersands.rs:7:15
+   |
+LL |     let _ = a and b;
+   |               ^^^ help: use `&&` to perform logical conjunction
+   |
+   = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
+
+error: `and` is not a logical operator
+  --> $DIR/issue-54109-and_instead_of_ampersands.rs:9:10
    |
 LL |     if a and b {
-   |     --   ^^^
-   |     |    |
-   |     |    expected `{`
-   |     |    help: use `&&` instead of `and` for the boolean operator
-   |     this `if` statement has a condition, but no block
+   |          ^^^ help: use `&&` to perform logical conjunction
+   |
+   = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
+
+error: `or` is not a logical operator
+  --> $DIR/issue-54109-and_instead_of_ampersands.rs:20:15
+   |
+LL |     let _ = a or b;
+   |               ^^ help: use `||` to perform logical disjunction
+   |
+   = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
 
-error: expected `{`, found `or`
-  --> $DIR/issue-54109-and_instead_of_ampersands.rs:13:10
+error: `or` is not a logical operator
+  --> $DIR/issue-54109-and_instead_of_ampersands.rs:22:10
    |
 LL |     if a or b {
-   |     --   ^^
-   |     |    |
-   |     |    expected `{`
-   |     |    help: use `||` instead of `or` for the boolean operator
-   |     this `if` statement has a condition, but no block
+   |          ^^ help: use `||` to perform logical disjunction
+   |
+   = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
 
-error: expected one of `!`, `)`, `,`, `.`, `::`, `?`, `{`, or an operator, found `and`
-  --> $DIR/issue-54109-and_instead_of_ampersands.rs:22:11
+error: `and` is not a logical operator
+  --> $DIR/issue-54109-and_instead_of_ampersands.rs:30:11
    |
 LL |     if (a and b) {
-   |           ^^^
-   |           |
-   |           expected one of 8 possible tokens
-   |           help: use `&&` instead of `and` for the boolean operator
+   |           ^^^ help: use `&&` to perform logical conjunction
+   |
+   = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
 
-error: expected one of `!`, `)`, `,`, `.`, `::`, `?`, `{`, or an operator, found `or`
-  --> $DIR/issue-54109-and_instead_of_ampersands.rs:31:11
+error: `or` is not a logical operator
+  --> $DIR/issue-54109-and_instead_of_ampersands.rs:38:11
    |
 LL |     if (a or b) {
-   |           ^^
-   |           |
-   |           expected one of 8 possible tokens
-   |           help: use `||` instead of `or` for the boolean operator
+   |           ^^ help: use `||` to perform logical disjunction
+   |
+   = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
 
-error: expected one of `!`, `.`, `::`, `?`, `{`, or an operator, found `and`
-  --> $DIR/issue-54109-and_instead_of_ampersands.rs:40:13
+error: `and` is not a logical operator
+  --> $DIR/issue-54109-and_instead_of_ampersands.rs:46:13
    |
 LL |     while a and b {
-   |             ^^^
-   |             |
-   |             expected one of `!`, `.`, `::`, `?`, `{`, or an operator
-   |             help: use `&&` instead of `and` for the boolean operator
+   |             ^^^ help: use `&&` to perform logical conjunction
+   |
+   = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
 
-error: expected one of `!`, `.`, `::`, `?`, `{`, or an operator, found `or`
-  --> $DIR/issue-54109-and_instead_of_ampersands.rs:49:13
+error: `or` is not a logical operator
+  --> $DIR/issue-54109-and_instead_of_ampersands.rs:54:13
    |
 LL |     while a or b {
-   |             ^^
-   |             |
-   |             expected one of `!`, `.`, `::`, `?`, `{`, or an operator
-   |             help: use `||` instead of `or` for the boolean operator
+   |             ^^ help: use `||` to perform logical disjunction
+   |
+   = note: unlike in e.g., python and PHP, `&&` and `||` are used for logical operators
+
+error[E0308]: mismatched types
+  --> $DIR/issue-54109-and_instead_of_ampersands.rs:13:33
+   |
+LL |     let _recovery_witness: () = 0;
+   |                            --   ^ expected `()`, found integer
+   |                            |
+   |                            expected due to this
 
-error: aborting due to 6 previous errors
+error: aborting due to 9 previous errors
 
+For more information about this error, try `rustc --explain E0308`.
diff --git a/src/test/ui/parser/attr-stmt-expr-attr-bad-2.rs b/src/test/ui/parser/attr-stmt-expr-attr-bad-2.rs
new file mode 100644
index 00000000000..e5ac59ae463
--- /dev/null
+++ b/src/test/ui/parser/attr-stmt-expr-attr-bad-2.rs
@@ -0,0 +1,2 @@
+#[cfg(FALSE)] fn e() { let _ = x.#![attr]foo(); }
+//~^ ERROR unexpected token: `#`
diff --git a/src/test/ui/parser/attr-stmt-expr-attr-bad-2.stderr b/src/test/ui/parser/attr-stmt-expr-attr-bad-2.stderr
new file mode 100644
index 00000000000..ca1043250ba
--- /dev/null
+++ b/src/test/ui/parser/attr-stmt-expr-attr-bad-2.stderr
@@ -0,0 +1,8 @@
+error: unexpected token: `#`
+  --> $DIR/attr-stmt-expr-attr-bad-2.rs:1:34
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = x.#![attr]foo(); }
+   |                                  ^
+
+error: aborting due to previous error
+
diff --git a/src/test/ui/parser/attr-stmt-expr-attr-bad-3.rs b/src/test/ui/parser/attr-stmt-expr-attr-bad-3.rs
new file mode 100644
index 00000000000..7dc71af52f4
--- /dev/null
+++ b/src/test/ui/parser/attr-stmt-expr-attr-bad-3.rs
@@ -0,0 +1,2 @@
+#[cfg(FALSE)] fn e() { let _ = x.#[attr]foo(); }
+//~^ ERROR unexpected token: `#`
diff --git a/src/test/ui/parser/attr-stmt-expr-attr-bad-3.stderr b/src/test/ui/parser/attr-stmt-expr-attr-bad-3.stderr
new file mode 100644
index 00000000000..ab9366d042a
--- /dev/null
+++ b/src/test/ui/parser/attr-stmt-expr-attr-bad-3.stderr
@@ -0,0 +1,8 @@
+error: unexpected token: `#`
+  --> $DIR/attr-stmt-expr-attr-bad-3.rs:1:34
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = x.#[attr]foo(); }
+   |                                  ^
+
+error: aborting due to previous error
+
diff --git a/src/test/ui/parser/attr-stmt-expr-attr-bad.rs b/src/test/ui/parser/attr-stmt-expr-attr-bad.rs
new file mode 100644
index 00000000000..ef10010ed0e
--- /dev/null
+++ b/src/test/ui/parser/attr-stmt-expr-attr-bad.rs
@@ -0,0 +1,107 @@
+fn main() {}
+
+#[cfg(FALSE)] fn e() { let _ = box #![attr] 0; }
+//~^ ERROR an inner attribute is not permitted in this context
+#[cfg(FALSE)] fn e() { let _ = [#[attr]]; }
+//~^ ERROR expected expression, found `]`
+#[cfg(FALSE)] fn e() { let _ = foo#[attr](); }
+//~^ ERROR expected one of
+#[cfg(FALSE)] fn e() { let _ = foo(#![attr]); }
+//~^ ERROR an inner attribute is not permitted in this context
+//~| ERROR expected expression, found `)`
+#[cfg(FALSE)] fn e() { let _ = x.foo(#![attr]); }
+//~^ ERROR an inner attribute is not permitted in this context
+//~| ERROR expected expression, found `)`
+#[cfg(FALSE)] fn e() { let _ = 0 + #![attr] 0; }
+//~^ ERROR an inner attribute is not permitted in this context
+#[cfg(FALSE)] fn e() { let _ = !#![attr] 0; }
+//~^ ERROR an inner attribute is not permitted in this context
+#[cfg(FALSE)] fn e() { let _ = -#![attr] 0; }
+//~^ ERROR an inner attribute is not permitted in this context
+#[cfg(FALSE)] fn e() { let _ = x #![attr] as Y; }
+//~^ ERROR expected one of
+#[cfg(FALSE)] fn e() { let _ = || #![attr] foo; }
+//~^ ERROR an inner attribute is not permitted in this context
+#[cfg(FALSE)] fn e() { let _ = move || #![attr] foo; }
+//~^ ERROR an inner attribute is not permitted in this context
+#[cfg(FALSE)] fn e() { let _ = || #![attr] {foo}; }
+//~^ ERROR an inner attribute is not permitted in this context
+#[cfg(FALSE)] fn e() { let _ = move || #![attr] {foo}; }
+//~^ ERROR an inner attribute is not permitted in this context
+#[cfg(FALSE)] fn e() { let _ = #[attr] ..#[attr] 0; }
+//~^ ERROR expected expression, found `..`
+#[cfg(FALSE)] fn e() { let _ = #[attr] ..; }
+//~^ ERROR expected expression, found `..`
+#[cfg(FALSE)] fn e() { let _ = #[attr] &#![attr] 0; }
+//~^ ERROR an inner attribute is not permitted in this context
+#[cfg(FALSE)] fn e() { let _ = #[attr] &mut #![attr] 0; }
+//~^ ERROR an inner attribute is not permitted in this context
+#[cfg(FALSE)] fn e() { let _ = #[attr] if 0 {}; }
+//~^ ERROR attributes are not yet allowed on `if` expressions
+#[cfg(FALSE)] fn e() { let _ = if 0 #[attr] {}; }
+//~^ ERROR expected `{`, found `#`
+#[cfg(FALSE)] fn e() { let _ = if 0 {#![attr]}; }
+//~^ ERROR an inner attribute is not permitted in this context
+#[cfg(FALSE)] fn e() { let _ = if 0 {} #[attr] else {}; }
+//~^ ERROR expected one of
+#[cfg(FALSE)] fn e() { let _ = if 0 {} else #[attr] {}; }
+//~^ ERROR expected `{`, found `#`
+#[cfg(FALSE)] fn e() { let _ = if 0 {} else {#![attr]}; }
+//~^ ERROR an inner attribute is not permitted in this context
+#[cfg(FALSE)] fn e() { let _ = if 0 {} else #[attr] if 0 {}; }
+//~^ ERROR attributes are not yet allowed on `if` expressions
+//~| ERROR expected `{`, found `#`
+#[cfg(FALSE)] fn e() { let _ = if 0 {} else if 0 #[attr] {}; }
+//~^ ERROR expected `{`, found `#`
+#[cfg(FALSE)] fn e() { let _ = if 0 {} else if 0 {#![attr]}; }
+//~^ ERROR an inner attribute is not permitted in this context
+#[cfg(FALSE)] fn e() { let _ = #[attr] if let _ = 0 {}; }
+//~^ ERROR attributes are not yet allowed on `if` expressions
+#[cfg(FALSE)] fn e() { let _ = if let _ = 0 #[attr] {}; }
+//~^ ERROR expected `{`, found `#`
+#[cfg(FALSE)] fn e() { let _ = if let _ = 0 {#![attr]}; }
+//~^ ERROR an inner attribute is not permitted in this context
+#[cfg(FALSE)] fn e() { let _ = if let _ = 0 {} #[attr] else {}; }
+//~^ ERROR expected one of
+#[cfg(FALSE)] fn e() { let _ = if let _ = 0 {} else #[attr] {}; }
+//~^ ERROR expected `{`, found `#`
+#[cfg(FALSE)] fn e() { let _ = if let _ = 0 {} else {#![attr]}; }
+//~^ ERROR an inner attribute is not permitted in this context
+#[cfg(FALSE)] fn e() { let _ = if let _ = 0 {} else #[attr] if let _ = 0 {}; }
+//~^ ERROR attributes are not yet allowed on `if` expressions
+//~| ERROR expected `{`, found `#`
+#[cfg(FALSE)] fn e() { let _ = if let _ = 0 {} else if let _ = 0 #[attr] {}; }
+//~^ ERROR expected `{`, found `#`
+#[cfg(FALSE)] fn e() { let _ = if let _ = 0 {} else if let _ = 0 {#![attr]}; }
+//~^ ERROR an inner attribute is not permitted in this context
+
+#[cfg(FALSE)] fn s() { #[attr] #![attr] let _ = 0; }
+//~^ ERROR an inner attribute is not permitted following an outer attribute
+#[cfg(FALSE)] fn s() { #[attr] #![attr] 0; }
+//~^ ERROR an inner attribute is not permitted following an outer attribute
+#[cfg(FALSE)] fn s() { #[attr] #![attr] foo!(); }
+//~^ ERROR an inner attribute is not permitted following an outer attribute
+#[cfg(FALSE)] fn s() { #[attr] #![attr] foo![]; }
+//~^ ERROR an inner attribute is not permitted following an outer attribute
+#[cfg(FALSE)] fn s() { #[attr] #![attr] foo!{}; }
+//~^ ERROR an inner attribute is not permitted following an outer attribute
+
+// FIXME: Allow attributes in pattern constexprs?
+// note: requires parens in patterns to allow disambiguation
+
+#[cfg(FALSE)] fn e() { match 0 { 0..=#[attr] 10 => () } }
+//~^ ERROR `X..=` range patterns are not supported
+//~| ERROR expected one of `=>`, `if`, or `|`, found `#`
+#[cfg(FALSE)] fn e() { match 0 { 0..=#[attr] -10 => () } }
+//~^ ERROR `X..=` range patterns are not supported
+//~| ERROR expected one of `=>`, `if`, or `|`, found `#`
+#[cfg(FALSE)] fn e() { match 0 { 0..=-#[attr] 10 => () } }
+//~^ ERROR unexpected token: `#`
+#[cfg(FALSE)] fn e() { match 0 { 0..=#[attr] FOO => () } }
+//~^ ERROR `X..=` range patterns are not supported
+//~| ERROR expected one of `=>`, `if`, or `|`, found `#`
+
+// make sure we don't catch this bug again...
+#[cfg(FALSE)] fn e() { { fn foo() { #[attr]; } } }
+//~^ ERROR expected statement after outer attribute
+#[cfg(FALSE)] fn e() { { fn foo() { #[attr] } } }
diff --git a/src/test/ui/parser/attr-stmt-expr-attr-bad.stderr b/src/test/ui/parser/attr-stmt-expr-attr-bad.stderr
new file mode 100644
index 00000000000..9a0d3176714
--- /dev/null
+++ b/src/test/ui/parser/attr-stmt-expr-attr-bad.stderr
@@ -0,0 +1,390 @@
+error: an inner attribute is not permitted in this context
+  --> $DIR/attr-stmt-expr-attr-bad.rs:3:36
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = box #![attr] 0; }
+   |                                    ^^^^^^^^
+   |
+   = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files. Outer attributes, like `#[test]`, annotate the item following them.
+
+error: expected expression, found `]`
+  --> $DIR/attr-stmt-expr-attr-bad.rs:5:40
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = [#[attr]]; }
+   |                                        ^ expected expression
+
+error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, or an operator, found `#`
+  --> $DIR/attr-stmt-expr-attr-bad.rs:7:35
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = foo#[attr](); }
+   |                                   ^ expected one of 7 possible tokens
+
+error: an inner attribute is not permitted in this context
+  --> $DIR/attr-stmt-expr-attr-bad.rs:9:36
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = foo(#![attr]); }
+   |                                    ^^^^^^^^
+   |
+   = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files. Outer attributes, like `#[test]`, annotate the item following them.
+
+error: expected expression, found `)`
+  --> $DIR/attr-stmt-expr-attr-bad.rs:9:44
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = foo(#![attr]); }
+   |                                            ^ expected expression
+
+error: an inner attribute is not permitted in this context
+  --> $DIR/attr-stmt-expr-attr-bad.rs:12:38
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = x.foo(#![attr]); }
+   |                                      ^^^^^^^^
+   |
+   = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files. Outer attributes, like `#[test]`, annotate the item following them.
+
+error: expected expression, found `)`
+  --> $DIR/attr-stmt-expr-attr-bad.rs:12:46
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = x.foo(#![attr]); }
+   |                                              ^ expected expression
+
+error: an inner attribute is not permitted in this context
+  --> $DIR/attr-stmt-expr-attr-bad.rs:15:36
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = 0 + #![attr] 0; }
+   |                                    ^^^^^^^^
+   |
+   = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files. Outer attributes, like `#[test]`, annotate the item following them.
+
+error: an inner attribute is not permitted in this context
+  --> $DIR/attr-stmt-expr-attr-bad.rs:17:33
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = !#![attr] 0; }
+   |                                 ^^^^^^^^
+   |
+   = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files. Outer attributes, like `#[test]`, annotate the item following them.
+
+error: an inner attribute is not permitted in this context
+  --> $DIR/attr-stmt-expr-attr-bad.rs:19:33
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = -#![attr] 0; }
+   |                                 ^^^^^^^^
+   |
+   = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files. Outer attributes, like `#[test]`, annotate the item following them.
+
+error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, or an operator, found `#`
+  --> $DIR/attr-stmt-expr-attr-bad.rs:21:34
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = x #![attr] as Y; }
+   |                                  ^ expected one of 7 possible tokens
+
+error: an inner attribute is not permitted in this context
+  --> $DIR/attr-stmt-expr-attr-bad.rs:23:35
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = || #![attr] foo; }
+   |                                   ^^^^^^^^
+   |
+   = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files. Outer attributes, like `#[test]`, annotate the item following them.
+
+error: an inner attribute is not permitted in this context
+  --> $DIR/attr-stmt-expr-attr-bad.rs:25:40
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = move || #![attr] foo; }
+   |                                        ^^^^^^^^
+   |
+   = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files. Outer attributes, like `#[test]`, annotate the item following them.
+
+error: an inner attribute is not permitted in this context
+  --> $DIR/attr-stmt-expr-attr-bad.rs:27:35
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = || #![attr] {foo}; }
+   |                                   ^^^^^^^^
+   |
+   = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files. Outer attributes, like `#[test]`, annotate the item following them.
+
+error: an inner attribute is not permitted in this context
+  --> $DIR/attr-stmt-expr-attr-bad.rs:29:40
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = move || #![attr] {foo}; }
+   |                                        ^^^^^^^^
+   |
+   = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files. Outer attributes, like `#[test]`, annotate the item following them.
+
+error: expected expression, found `..`
+  --> $DIR/attr-stmt-expr-attr-bad.rs:31:40
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = #[attr] ..#[attr] 0; }
+   |                                        ^^ expected expression
+
+error: expected expression, found `..`
+  --> $DIR/attr-stmt-expr-attr-bad.rs:33:40
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = #[attr] ..; }
+   |                                        ^^ expected expression
+
+error: an inner attribute is not permitted in this context
+  --> $DIR/attr-stmt-expr-attr-bad.rs:35:41
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = #[attr] &#![attr] 0; }
+   |                                         ^^^^^^^^
+   |
+   = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files. Outer attributes, like `#[test]`, annotate the item following them.
+
+error: an inner attribute is not permitted in this context
+  --> $DIR/attr-stmt-expr-attr-bad.rs:37:45
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = #[attr] &mut #![attr] 0; }
+   |                                             ^^^^^^^^
+   |
+   = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files. Outer attributes, like `#[test]`, annotate the item following them.
+
+error: attributes are not yet allowed on `if` expressions
+  --> $DIR/attr-stmt-expr-attr-bad.rs:39:32
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = #[attr] if 0 {}; }
+   |                                ^^^^^^^
+
+error: expected `{`, found `#`
+  --> $DIR/attr-stmt-expr-attr-bad.rs:41:37
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = if 0 #[attr] {}; }
+   |                                --   ^       --- help: try placing this code inside a block: `{ {}; }`
+   |                                |    |
+   |                                |    expected `{`
+   |                                this `if` statement has a condition, but no block
+
+error: an inner attribute is not permitted in this context
+  --> $DIR/attr-stmt-expr-attr-bad.rs:43:38
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = if 0 {#![attr]}; }
+   |                                      ^^^^^^^^
+   |
+   = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files. Outer attributes, like `#[test]`, annotate the item following them.
+
+error: expected one of `.`, `;`, `?`, `else`, or an operator, found `#`
+  --> $DIR/attr-stmt-expr-attr-bad.rs:45:40
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = if 0 {} #[attr] else {}; }
+   |                                        ^ expected one of `.`, `;`, `?`, `else`, or an operator
+
+error: expected `{`, found `#`
+  --> $DIR/attr-stmt-expr-attr-bad.rs:47:45
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = if 0 {} else #[attr] {}; }
+   |                                             ^       --- help: try placing this code inside a block: `{ {}; }`
+   |                                             |
+   |                                             expected `{`
+
+error: an inner attribute is not permitted in this context
+  --> $DIR/attr-stmt-expr-attr-bad.rs:49:46
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = if 0 {} else {#![attr]}; }
+   |                                              ^^^^^^^^
+   |
+   = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files. Outer attributes, like `#[test]`, annotate the item following them.
+
+error: attributes are not yet allowed on `if` expressions
+  --> $DIR/attr-stmt-expr-attr-bad.rs:51:45
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = if 0 {} else #[attr] if 0 {}; }
+   |                                             ^^^^^^^
+
+error: expected `{`, found `#`
+  --> $DIR/attr-stmt-expr-attr-bad.rs:51:45
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = if 0 {} else #[attr] if 0 {}; }
+   |                                             ^       -------- help: try placing this code inside a block: `{ if 0 {}; }`
+   |                                             |
+   |                                             expected `{`
+
+error: expected `{`, found `#`
+  --> $DIR/attr-stmt-expr-attr-bad.rs:54:50
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = if 0 {} else if 0 #[attr] {}; }
+   |                                             --   ^       --- help: try placing this code inside a block: `{ {}; }`
+   |                                             |    |
+   |                                             |    expected `{`
+   |                                             this `if` statement has a condition, but no block
+
+error: an inner attribute is not permitted in this context
+  --> $DIR/attr-stmt-expr-attr-bad.rs:56:51
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = if 0 {} else if 0 {#![attr]}; }
+   |                                                   ^^^^^^^^
+   |
+   = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files. Outer attributes, like `#[test]`, annotate the item following them.
+
+error: attributes are not yet allowed on `if` expressions
+  --> $DIR/attr-stmt-expr-attr-bad.rs:58:32
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = #[attr] if let _ = 0 {}; }
+   |                                ^^^^^^^
+
+error: expected `{`, found `#`
+  --> $DIR/attr-stmt-expr-attr-bad.rs:60:45
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = if let _ = 0 #[attr] {}; }
+   |                                --           ^       --- help: try placing this code inside a block: `{ {}; }`
+   |                                |            |
+   |                                |            expected `{`
+   |                                this `if` statement has a condition, but no block
+
+error: an inner attribute is not permitted in this context
+  --> $DIR/attr-stmt-expr-attr-bad.rs:62:46
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = if let _ = 0 {#![attr]}; }
+   |                                              ^^^^^^^^
+   |
+   = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files. Outer attributes, like `#[test]`, annotate the item following them.
+
+error: expected one of `.`, `;`, `?`, `else`, or an operator, found `#`
+  --> $DIR/attr-stmt-expr-attr-bad.rs:64:48
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = if let _ = 0 {} #[attr] else {}; }
+   |                                                ^ expected one of `.`, `;`, `?`, `else`, or an operator
+
+error: expected `{`, found `#`
+  --> $DIR/attr-stmt-expr-attr-bad.rs:66:53
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = if let _ = 0 {} else #[attr] {}; }
+   |                                                     ^       --- help: try placing this code inside a block: `{ {}; }`
+   |                                                     |
+   |                                                     expected `{`
+
+error: an inner attribute is not permitted in this context
+  --> $DIR/attr-stmt-expr-attr-bad.rs:68:54
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = if let _ = 0 {} else {#![attr]}; }
+   |                                                      ^^^^^^^^
+   |
+   = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files. Outer attributes, like `#[test]`, annotate the item following them.
+
+error: attributes are not yet allowed on `if` expressions
+  --> $DIR/attr-stmt-expr-attr-bad.rs:70:53
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = if let _ = 0 {} else #[attr] if let _ = 0 {}; }
+   |                                                     ^^^^^^^
+
+error: expected `{`, found `#`
+  --> $DIR/attr-stmt-expr-attr-bad.rs:70:53
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = if let _ = 0 {} else #[attr] if let _ = 0 {}; }
+   |                                                     ^       ---------------- help: try placing this code inside a block: `{ if let _ = 0 {}; }`
+   |                                                     |
+   |                                                     expected `{`
+
+error: expected `{`, found `#`
+  --> $DIR/attr-stmt-expr-attr-bad.rs:73:66
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = if let _ = 0 {} else if let _ = 0 #[attr] {}; }
+   |                                                     --           ^       --- help: try placing this code inside a block: `{ {}; }`
+   |                                                     |            |
+   |                                                     |            expected `{`
+   |                                                     this `if` statement has a condition, but no block
+
+error: an inner attribute is not permitted in this context
+  --> $DIR/attr-stmt-expr-attr-bad.rs:75:67
+   |
+LL | #[cfg(FALSE)] fn e() { let _ = if let _ = 0 {} else if let _ = 0 {#![attr]}; }
+   |                                                                   ^^^^^^^^
+   |
+   = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files. Outer attributes, like `#[test]`, annotate the item following them.
+
+error: an inner attribute is not permitted following an outer attribute
+  --> $DIR/attr-stmt-expr-attr-bad.rs:78:32
+   |
+LL | #[cfg(FALSE)] fn s() { #[attr] #![attr] let _ = 0; }
+   |                        ------- ^^^^^^^^ not permitted following an outer attibute
+   |                        |
+   |                        previous outer attribute
+   |
+   = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files. Outer attributes, like `#[test]`, annotate the item following them.
+
+error: an inner attribute is not permitted following an outer attribute
+  --> $DIR/attr-stmt-expr-attr-bad.rs:80:32
+   |
+LL | #[cfg(FALSE)] fn s() { #[attr] #![attr] 0; }
+   |                        ------- ^^^^^^^^ not permitted following an outer attibute
+   |                        |
+   |                        previous outer attribute
+   |
+   = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files. Outer attributes, like `#[test]`, annotate the item following them.
+
+error: an inner attribute is not permitted following an outer attribute
+  --> $DIR/attr-stmt-expr-attr-bad.rs:82:32
+   |
+LL | #[cfg(FALSE)] fn s() { #[attr] #![attr] foo!(); }
+   |                        ------- ^^^^^^^^ not permitted following an outer attibute
+   |                        |
+   |                        previous outer attribute
+   |
+   = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files. Outer attributes, like `#[test]`, annotate the item following them.
+
+error: an inner attribute is not permitted following an outer attribute
+  --> $DIR/attr-stmt-expr-attr-bad.rs:84:32
+   |
+LL | #[cfg(FALSE)] fn s() { #[attr] #![attr] foo![]; }
+   |                        ------- ^^^^^^^^ not permitted following an outer attibute
+   |                        |
+   |                        previous outer attribute
+   |
+   = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files. Outer attributes, like `#[test]`, annotate the item following them.
+
+error: an inner attribute is not permitted following an outer attribute
+  --> $DIR/attr-stmt-expr-attr-bad.rs:86:32
+   |
+LL | #[cfg(FALSE)] fn s() { #[attr] #![attr] foo!{}; }
+   |                        ------- ^^^^^^^^ not permitted following an outer attibute
+   |                        |
+   |                        previous outer attribute
+   |
+   = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files. Outer attributes, like `#[test]`, annotate the item following them.
+
+error: `X..=` range patterns are not supported
+  --> $DIR/attr-stmt-expr-attr-bad.rs:92:34
+   |
+LL | #[cfg(FALSE)] fn e() { match 0 { 0..=#[attr] 10 => () } }
+   |                                  ^^^^ help: try using the maximum value for the type: `0..=MAX`
+
+error: expected one of `=>`, `if`, or `|`, found `#`
+  --> $DIR/attr-stmt-expr-attr-bad.rs:92:38
+   |
+LL | #[cfg(FALSE)] fn e() { match 0 { 0..=#[attr] 10 => () } }
+   |                                      ^ expected one of `=>`, `if`, or `|`
+
+error: `X..=` range patterns are not supported
+  --> $DIR/attr-stmt-expr-attr-bad.rs:95:34
+   |
+LL | #[cfg(FALSE)] fn e() { match 0 { 0..=#[attr] -10 => () } }
+   |                                  ^^^^ help: try using the maximum value for the type: `0..=MAX`
+
+error: expected one of `=>`, `if`, or `|`, found `#`
+  --> $DIR/attr-stmt-expr-attr-bad.rs:95:38
+   |
+LL | #[cfg(FALSE)] fn e() { match 0 { 0..=#[attr] -10 => () } }
+   |                                      ^ expected one of `=>`, `if`, or `|`
+
+error: unexpected token: `#`
+  --> $DIR/attr-stmt-expr-attr-bad.rs:98:39
+   |
+LL | #[cfg(FALSE)] fn e() { match 0 { 0..=-#[attr] 10 => () } }
+   |                                       ^
+
+error: `X..=` range patterns are not supported
+  --> $DIR/attr-stmt-expr-attr-bad.rs:100:34
+   |
+LL | #[cfg(FALSE)] fn e() { match 0 { 0..=#[attr] FOO => () } }
+   |                                  ^^^^ help: try using the maximum value for the type: `0..=MAX`
+
+error: expected one of `=>`, `if`, or `|`, found `#`
+  --> $DIR/attr-stmt-expr-attr-bad.rs:100:38
+   |
+LL | #[cfg(FALSE)] fn e() { match 0 { 0..=#[attr] FOO => () } }
+   |                                      ^ expected one of `=>`, `if`, or `|`
+
+error: expected statement after outer attribute
+  --> $DIR/attr-stmt-expr-attr-bad.rs:105:44
+   |
+LL | #[cfg(FALSE)] fn e() { { fn foo() { #[attr]; } } }
+   |                                            ^
+
+error: aborting due to 52 previous errors
+
diff --git a/src/test/ui/parser/do-catch-suggests-try.rs b/src/test/ui/parser/do-catch-suggests-try.rs
index d805ab75882..f64568d06e9 100644
--- a/src/test/ui/parser/do-catch-suggests-try.rs
+++ b/src/test/ui/parser/do-catch-suggests-try.rs
@@ -1,5 +1,10 @@
+#![feature(try_blocks)]
+
 fn main() {
     let _: Option<()> = do catch {};
     //~^ ERROR found removed `do catch` syntax
-    //~^^ HELP following RFC #2388, the new non-placeholder syntax is `try`
+    //~| replace with the new syntax
+    //~| following RFC #2388, the new non-placeholder syntax is `try`
+
+    let _recovery_witness: () = 1; //~ ERROR mismatched types
 }
diff --git a/src/test/ui/parser/do-catch-suggests-try.stderr b/src/test/ui/parser/do-catch-suggests-try.stderr
index e151d4cf8a6..cd8907b7eac 100644
--- a/src/test/ui/parser/do-catch-suggests-try.stderr
+++ b/src/test/ui/parser/do-catch-suggests-try.stderr
@@ -1,10 +1,19 @@
 error: found removed `do catch` syntax
-  --> $DIR/do-catch-suggests-try.rs:2:25
+  --> $DIR/do-catch-suggests-try.rs:4:25
    |
 LL |     let _: Option<()> = do catch {};
-   |                         ^^
+   |                         ^^^^^^^^ help: replace with the new syntax: `try`
    |
-   = help: following RFC #2388, the new non-placeholder syntax is `try`
+   = note: following RFC #2388, the new non-placeholder syntax is `try`
 
-error: aborting due to previous error
+error[E0308]: mismatched types
+  --> $DIR/do-catch-suggests-try.rs:9:33
+   |
+LL |     let _recovery_witness: () = 1;
+   |                            --   ^ expected `()`, found integer
+   |                            |
+   |                            expected due to this
+
+error: aborting due to 2 previous errors
 
+For more information about this error, try `rustc --explain E0308`.
diff --git a/src/test/ui/parser/issue-65257-invalid-var-decl-recovery.rs b/src/test/ui/parser/issue-65257-invalid-var-decl-recovery.rs
new file mode 100644
index 00000000000..c1826f8caae
--- /dev/null
+++ b/src/test/ui/parser/issue-65257-invalid-var-decl-recovery.rs
@@ -0,0 +1,21 @@
+fn main() {
+    auto n = 0;//~ ERROR invalid variable declaration
+    //~^ HELP write `let` instead of `auto` to introduce a new variable
+    auto m;//~ ERROR invalid variable declaration
+    //~^ HELP write `let` instead of `auto` to introduce a new variable
+    m = 0;
+
+    var n = 0;//~ ERROR invalid variable declaration
+    //~^ HELP write `let` instead of `var` to introduce a new variable
+    var m;//~ ERROR invalid variable declaration
+    //~^ HELP write `let` instead of `var` to introduce a new variable
+    m = 0;
+
+    mut n = 0;//~ ERROR invalid variable declaration
+    //~^ HELP missing keyword
+    mut var;//~ ERROR invalid variable declaration
+    //~^ HELP missing keyword
+    var = 0;
+
+    let _recovery_witness: () = 0; //~ ERROR mismatched types
+}
diff --git a/src/test/ui/parser/issue-65257-invalid-var-decl-recovery.stderr b/src/test/ui/parser/issue-65257-invalid-var-decl-recovery.stderr
new file mode 100644
index 00000000000..ad72dd30542
--- /dev/null
+++ b/src/test/ui/parser/issue-65257-invalid-var-decl-recovery.stderr
@@ -0,0 +1,67 @@
+error: invalid variable declaration
+  --> $DIR/issue-65257-invalid-var-decl-recovery.rs:2:5
+   |
+LL |     auto n = 0;
+   |     ^^^^
+   |
+help: write `let` instead of `auto` to introduce a new variable
+   |
+LL |     let n = 0;
+   |     ^^^
+
+error: invalid variable declaration
+  --> $DIR/issue-65257-invalid-var-decl-recovery.rs:4:5
+   |
+LL |     auto m;
+   |     ^^^^
+   |
+help: write `let` instead of `auto` to introduce a new variable
+   |
+LL |     let m;
+   |     ^^^
+
+error: invalid variable declaration
+  --> $DIR/issue-65257-invalid-var-decl-recovery.rs:8:5
+   |
+LL |     var n = 0;
+   |     ^^^
+   |
+help: write `let` instead of `var` to introduce a new variable
+   |
+LL |     let n = 0;
+   |     ^^^
+
+error: invalid variable declaration
+  --> $DIR/issue-65257-invalid-var-decl-recovery.rs:10:5
+   |
+LL |     var m;
+   |     ^^^
+   |
+help: write `let` instead of `var` to introduce a new variable
+   |
+LL |     let m;
+   |     ^^^
+
+error: invalid variable declaration
+  --> $DIR/issue-65257-invalid-var-decl-recovery.rs:14:5
+   |
+LL |     mut n = 0;
+   |     ^^^ help: missing keyword: `let mut`
+
+error: invalid variable declaration
+  --> $DIR/issue-65257-invalid-var-decl-recovery.rs:16:5
+   |
+LL |     mut var;
+   |     ^^^ help: missing keyword: `let mut`
+
+error[E0308]: mismatched types
+  --> $DIR/issue-65257-invalid-var-decl-recovery.rs:20:33
+   |
+LL |     let _recovery_witness: () = 0;
+   |                            --   ^ expected `()`, found integer
+   |                            |
+   |                            expected due to this
+
+error: aborting due to 7 previous errors
+
+For more information about this error, try `rustc --explain E0308`.
diff --git a/src/test/ui/parser/recover-labeled-non-block-expr.rs b/src/test/ui/parser/recover-labeled-non-block-expr.rs
new file mode 100644
index 00000000000..be92170acf0
--- /dev/null
+++ b/src/test/ui/parser/recover-labeled-non-block-expr.rs
@@ -0,0 +1,5 @@
+fn main() {
+    'label: 1 + 1; //~ ERROR expected `while`, `for`, `loop` or `{` after a label
+
+    let _recovery_witness: () = 0; //~ ERROR mismatched types
+}
diff --git a/src/test/ui/parser/recover-labeled-non-block-expr.stderr b/src/test/ui/parser/recover-labeled-non-block-expr.stderr
new file mode 100644
index 00000000000..771a915288c
--- /dev/null
+++ b/src/test/ui/parser/recover-labeled-non-block-expr.stderr
@@ -0,0 +1,17 @@
+error: expected `while`, `for`, `loop` or `{` after a label
+  --> $DIR/recover-labeled-non-block-expr.rs:2:13
+   |
+LL |     'label: 1 + 1;
+   |             ^ expected `while`, `for`, `loop` or `{` after a label
+
+error[E0308]: mismatched types
+  --> $DIR/recover-labeled-non-block-expr.rs:4:33
+   |
+LL |     let _recovery_witness: () = 0;
+   |                            --   ^ expected `()`, found integer
+   |                            |
+   |                            expected due to this
+
+error: aborting due to 2 previous errors
+
+For more information about this error, try `rustc --explain E0308`.
diff --git a/src/test/ui/parser/recovery-attr-on-if.rs b/src/test/ui/parser/recovery-attr-on-if.rs
new file mode 100644
index 00000000000..0d1f5be7b49
--- /dev/null
+++ b/src/test/ui/parser/recovery-attr-on-if.rs
@@ -0,0 +1,9 @@
+fn main() {
+    #[attr] if true {};
+    //~^ ERROR cannot find attribute
+    //~| ERROR attributes are not yet allowed on `if` expressions
+    #[attr] if true {};
+    //~^ ERROR cannot find attribute
+    //~| ERROR attributes are not yet allowed on `if` expressions
+    let _recovery_witness: () = 0; //~ ERROR mismatched types
+}
diff --git a/src/test/ui/parser/recovery-attr-on-if.stderr b/src/test/ui/parser/recovery-attr-on-if.stderr
new file mode 100644
index 00000000000..a02846827c9
--- /dev/null
+++ b/src/test/ui/parser/recovery-attr-on-if.stderr
@@ -0,0 +1,35 @@
+error: attributes are not yet allowed on `if` expressions
+  --> $DIR/recovery-attr-on-if.rs:2:5
+   |
+LL |     #[attr] if true {};
+   |     ^^^^^^^
+
+error: attributes are not yet allowed on `if` expressions
+  --> $DIR/recovery-attr-on-if.rs:5:5
+   |
+LL |     #[attr] if true {};
+   |     ^^^^^^^
+
+error: cannot find attribute `attr` in this scope
+  --> $DIR/recovery-attr-on-if.rs:5:7
+   |
+LL |     #[attr] if true {};
+   |       ^^^^
+
+error: cannot find attribute `attr` in this scope
+  --> $DIR/recovery-attr-on-if.rs:2:7
+   |
+LL |     #[attr] if true {};
+   |       ^^^^
+
+error[E0308]: mismatched types
+  --> $DIR/recovery-attr-on-if.rs:8:33
+   |
+LL |     let _recovery_witness: () = 0;
+   |                            --   ^ expected `()`, found integer
+   |                            |
+   |                            expected due to this
+
+error: aborting due to 5 previous errors
+
+For more information about this error, try `rustc --explain E0308`.
diff --git a/src/test/ui/parser/stmt_expr_attrs_placement.rs b/src/test/ui/parser/stmt_expr_attrs_placement.rs
new file mode 100644
index 00000000000..b8a794f4b92
--- /dev/null
+++ b/src/test/ui/parser/stmt_expr_attrs_placement.rs
@@ -0,0 +1,22 @@
+#![feature(stmt_expr_attributes)]
+
+// Test that various placements of the inner attribute are parsed correctly,
+// or not.
+
+fn main() {
+    let a = #![allow(warnings)] (1, 2);
+    //~^ ERROR an inner attribute is not permitted in this context
+
+    let b = (#![allow(warnings)] 1, 2);
+
+    let c = {
+        #![allow(warnings)]
+        (#![allow(warnings)] 1, 2)
+    };
+
+    let d = {
+        #![allow(warnings)]
+        let e = (#![allow(warnings)] 1, 2);
+        e
+    };
+}
diff --git a/src/test/ui/parser/stmt_expr_attrs_placement.stderr b/src/test/ui/parser/stmt_expr_attrs_placement.stderr
new file mode 100644
index 00000000000..1886a0f9ba0
--- /dev/null
+++ b/src/test/ui/parser/stmt_expr_attrs_placement.stderr
@@ -0,0 +1,10 @@
+error: an inner attribute is not permitted in this context
+  --> $DIR/stmt_expr_attrs_placement.rs:7:13
+   |
+LL |     let a = #![allow(warnings)] (1, 2);
+   |             ^^^^^^^^^^^^^^^^^^^
+   |
+   = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files. Outer attributes, like `#[test]`, annotate the item following them.
+
+error: aborting due to previous error
+