about summary refs log tree commit diff
path: root/compiler/rustc_ast/src/util/classify.rs
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_ast/src/util/classify.rs')
-rw-r--r--compiler/rustc_ast/src/util/classify.rs193
1 files changed, 167 insertions, 26 deletions
diff --git a/compiler/rustc_ast/src/util/classify.rs b/compiler/rustc_ast/src/util/classify.rs
index f21a9cabb81..382c903625f 100644
--- a/compiler/rustc_ast/src/util/classify.rs
+++ b/compiler/rustc_ast/src/util/classify.rs
@@ -1,34 +1,97 @@
-//! Routines the parser uses to classify AST nodes
-
-// Predicates on exprs and stmts that the pretty-printer and parser use
+//! Routines the parser and pretty-printer use to classify AST nodes.
 
+use crate::ast::ExprKind::*;
 use crate::{ast, token::Delimiter};
 
-/// Does this expression require a semicolon to be treated
-/// as a statement? The negation of this: 'can this expression
-/// be used as a statement without a semicolon' -- is used
-/// as an early-bail-out in the parser so that, for instance,
-///     if true {...} else {...}
-///      |x| 5
-/// isn't parsed as (if true {...} else {...} | x) | 5
-pub fn expr_requires_semi_to_be_stmt(e: &ast::Expr) -> bool {
-    !matches!(
+/// This classification determines whether various syntactic positions break out
+/// of parsing the current expression (true) or continue parsing more of the
+/// same expression (false).
+///
+/// For example, it's relevant in the parsing of match arms:
+///
+/// ```ignore (illustrative)
+/// match ... {
+///     // Is this calling $e as a function, or is it the start of a new arm
+///     // with a tuple pattern?
+///     _ => $e (
+///             ^                                                          )
+///
+///     // Is this an Index operation, or new arm with a slice pattern?
+///     _ => $e [
+///             ^                                                          ]
+///
+///     // Is this a binary operator, or leading vert in a new arm? Same for
+///     // other punctuation which can either be a binary operator in
+///     // expression or unary operator in pattern, such as `&` and `-`.
+///     _ => $e |
+///             ^
+/// }
+/// ```
+///
+/// If $e is something like `{}` or `if … {}`, then terminate the current
+/// arm and parse a new arm.
+///
+/// If $e is something like `path::to` or `(…)`, continue parsing the same
+/// arm.
+///
+/// *Almost* the same classification is used as an early bail-out for parsing
+/// statements. See `expr_requires_semi_to_be_stmt`.
+pub fn expr_is_complete(e: &ast::Expr) -> bool {
+    matches!(
         e.kind,
-        ast::ExprKind::If(..)
-            | ast::ExprKind::Match(..)
-            | ast::ExprKind::Block(..)
-            | ast::ExprKind::While(..)
-            | ast::ExprKind::Loop(..)
-            | ast::ExprKind::ForLoop { .. }
-            | ast::ExprKind::TryBlock(..)
-            | ast::ExprKind::ConstBlock(..)
+        If(..)
+            | Match(..)
+            | Block(..)
+            | While(..)
+            | Loop(..)
+            | ForLoop { .. }
+            | TryBlock(..)
+            | ConstBlock(..)
     )
 }
 
-/// If an expression ends with `}`, returns the innermost expression ending in the `}`
-pub fn expr_trailing_brace(mut expr: &ast::Expr) -> Option<&ast::Expr> {
-    use ast::ExprKind::*;
+/// Does this expression require a semicolon to be treated as a statement?
+///
+/// The negation of this: "can this expression be used as a statement without a
+/// semicolon" -- is used as an early bail-out when parsing statements so that,
+/// for instance,
+///
+/// ```ignore (illustrative)
+/// if true {...} else {...}
+/// |x| 5
+/// ```
+///
+/// isn't parsed as `(if true {...} else {...} | x) | 5`.
+///
+/// Surprising special case: even though braced macro calls like `m! {}`
+/// normally do not introduce a boundary when found at the head of a match arm,
+/// they do terminate the parsing of a statement.
+///
+/// ```ignore (illustrative)
+/// match ... {
+///     _ => m! {} (),  // macro that expands to a function, which is then called
+/// }
+///
+/// let _ = { m! {} () };  // macro call followed by unit
+/// ```
+pub fn expr_requires_semi_to_be_stmt(e: &ast::Expr) -> bool {
+    match &e.kind {
+        MacCall(mac_call) => mac_call.args.delim != Delimiter::Brace,
+        _ => !expr_is_complete(e),
+    }
+}
+
+pub enum TrailingBrace<'a> {
+    /// Trailing brace in a macro call, like the one in `x as *const brace! {}`.
+    /// We will suggest changing the macro call to a different delimiter.
+    MacCall(&'a ast::MacCall),
+    /// Trailing brace in any other expression, such as `a + B {}`. We will
+    /// suggest wrapping the innermost expression in parentheses: `a + (B {})`.
+    Expr(&'a ast::Expr),
+}
 
+/// If an expression ends with `}`, returns the innermost expression ending in the `}`
+pub fn expr_trailing_brace(mut expr: &ast::Expr) -> Option<TrailingBrace<'_>> {
     loop {
         match &expr.kind {
             AddrOf(_, _, e)
@@ -57,10 +120,14 @@ pub fn expr_trailing_brace(mut expr: &ast::Expr) -> Option<&ast::Expr> {
             | Struct(..)
             | TryBlock(..)
             | While(..)
-            | ConstBlock(_) => break Some(expr),
+            | ConstBlock(_) => break Some(TrailingBrace::Expr(expr)),
+
+            Cast(_, ty) => {
+                break type_trailing_braced_mac_call(ty).map(TrailingBrace::MacCall);
+            }
 
             MacCall(mac) => {
-                break (mac.args.delim == Delimiter::Brace).then_some(expr);
+                break (mac.args.delim == Delimiter::Brace).then_some(TrailingBrace::MacCall(mac));
             }
 
             InlineAsm(_) | OffsetOf(_, _) | IncludedBytes(_) | FormatArgs(_) => {
@@ -77,7 +144,6 @@ pub fn expr_trailing_brace(mut expr: &ast::Expr) -> Option<&ast::Expr> {
             | MethodCall(_)
             | Tup(_)
             | Lit(_)
-            | Cast(_, _)
             | Type(_, _)
             | Await(_, _)
             | Field(_, _)
@@ -94,3 +160,78 @@ pub fn expr_trailing_brace(mut expr: &ast::Expr) -> Option<&ast::Expr> {
         }
     }
 }
+
+/// If the type's last token is `}`, it must be due to a braced macro call, such
+/// as in `*const brace! { ... }`. Returns that trailing macro call.
+fn type_trailing_braced_mac_call(mut ty: &ast::Ty) -> Option<&ast::MacCall> {
+    loop {
+        match &ty.kind {
+            ast::TyKind::MacCall(mac) => {
+                break (mac.args.delim == Delimiter::Brace).then_some(mac);
+            }
+
+            ast::TyKind::Ptr(mut_ty) | ast::TyKind::Ref(_, mut_ty) => {
+                ty = &mut_ty.ty;
+            }
+
+            ast::TyKind::BareFn(fn_ty) => match &fn_ty.decl.output {
+                ast::FnRetTy::Default(_) => break None,
+                ast::FnRetTy::Ty(ret) => ty = ret,
+            },
+
+            ast::TyKind::Path(_, path) => match path_return_type(path) {
+                Some(trailing_ty) => ty = trailing_ty,
+                None => break None,
+            },
+
+            ast::TyKind::TraitObject(bounds, _) | ast::TyKind::ImplTrait(_, bounds, _) => {
+                match bounds.last() {
+                    Some(ast::GenericBound::Trait(bound, _)) => {
+                        match path_return_type(&bound.trait_ref.path) {
+                            Some(trailing_ty) => ty = trailing_ty,
+                            None => break None,
+                        }
+                    }
+                    Some(ast::GenericBound::Outlives(_)) | None => break None,
+                }
+            }
+
+            ast::TyKind::Slice(..)
+            | ast::TyKind::Array(..)
+            | ast::TyKind::Never
+            | ast::TyKind::Tup(..)
+            | ast::TyKind::Paren(..)
+            | ast::TyKind::Typeof(..)
+            | ast::TyKind::Infer
+            | ast::TyKind::ImplicitSelf
+            | ast::TyKind::CVarArgs
+            | ast::TyKind::Pat(..)
+            | ast::TyKind::Dummy
+            | ast::TyKind::Err(..) => break None,
+
+            // These end in brace, but cannot occur in a let-else statement.
+            // They are only parsed as fields of a data structure. For the
+            // purpose of denying trailing braces in the expression of a
+            // let-else, we can disregard these.
+            ast::TyKind::AnonStruct(..) | ast::TyKind::AnonUnion(..) => break None,
+        }
+    }
+}
+
+/// Returns the trailing return type in the given path, if it has one.
+///
+/// ```ignore (illustrative)
+/// ::std::ops::FnOnce(&str) -> fn() -> *const c_void
+///                             ^^^^^^^^^^^^^^^^^^^^^
+/// ```
+fn path_return_type(path: &ast::Path) -> Option<&ast::Ty> {
+    let last_segment = path.segments.last()?;
+    let args = last_segment.args.as_ref()?;
+    match &**args {
+        ast::GenericArgs::Parenthesized(args) => match &args.output {
+            ast::FnRetTy::Default(_) => None,
+            ast::FnRetTy::Ty(ret) => Some(ret),
+        },
+        ast::GenericArgs::AngleBracketed(_) => None,
+    }
+}