//! Routines the parser and pretty-printer use to classify AST nodes. use crate::ast::ExprKind::*; use crate::ast::{self, MatchKind}; use crate::token::Delimiter; /// 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, If(..) | Match(..) | Block(..) | While(..) | Loop(..) | ForLoop { .. } | TryBlock(..) | ConstBlock(..) ) } /// 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), } } /// Returns whether the leftmost token of the given expression is the label of a /// labeled loop or block, such as in `'inner: loop { break 'inner 1 } + 1`. /// /// Such expressions are not allowed as the value of an unlabeled break. /// /// ```ignore (illustrative) /// 'outer: { /// break 'inner: loop { break 'inner 1 } + 1; // invalid syntax /// /// break 'outer 'inner: loop { break 'inner 1 } + 1; // okay /// /// break ('inner: loop { break 'inner 1 } + 1); // okay /// /// break ('inner: loop { break 'inner 1 }) + 1; // okay /// } /// ``` pub fn leading_labeled_expr(mut expr: &ast::Expr) -> bool { loop { match &expr.kind { Block(_, label) | ForLoop { label, .. } | Loop(_, label, _) | While(_, _, label) => { return label.is_some(); } Assign(e, _, _) | AssignOp(_, e, _) | Await(e, _) | Use(e, _) | Binary(_, e, _) | Call(e, _) | Cast(e, _) | Field(e, _) | Index(e, _, _) | Match(e, _, MatchKind::Postfix) | Range(Some(e), _, _) | Try(e) => { expr = e; } MethodCall(method_call) => { expr = &method_call.receiver; } AddrOf(..) | Array(..) | Become(..) | Break(..) | Closure(..) | ConstBlock(..) | Continue(..) | FormatArgs(..) | Gen(..) | If(..) | IncludedBytes(..) | InlineAsm(..) | Let(..) | Lit(..) | MacCall(..) | Match(_, _, MatchKind::Prefix) | OffsetOf(..) | Paren(..) | Path(..) | Range(None, _, _) | Repeat(..) | Ret(..) | Struct(..) | TryBlock(..) | Tup(..) | Type(..) | Unary(..) | Underscore | Yeet(..) | Yield(..) | UnsafeBinderCast(..) | Err(..) | Dummy => return false, } } } 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> { loop { match &expr.kind { AddrOf(_, _, e) | Assign(_, e, _) | AssignOp(_, _, e) | Binary(_, _, e) | Break(_, Some(e)) | Let(_, e, _, _) | Range(_, Some(e), _) | Ret(Some(e)) | Unary(_, e) | Yeet(Some(e)) | Become(e) => { expr = e; } Yield(kind) => match kind.expr() { Some(e) => expr = e, None => break None, }, Closure(closure) => { expr = &closure.body; } Gen(..) | Block(..) | ForLoop { .. } | If(..) | Loop(..) | Match(..) | Struct(..) | TryBlock(..) | While(..) | 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(TrailingBrace::MacCall(mac)); } InlineAsm(_) | OffsetOf(_, _) | IncludedBytes(_) | FormatArgs(_) => { // These should have been denied pre-expansion. break None; } Break(_, None) | Range(_, None, _) | Ret(None) | Array(_) | Call(_, _) | MethodCall(_) | Tup(_) | Lit(_) | Type(_, _) | Await(_, _) | Use(_, _) | Field(_, _) | Index(_, _, _) | Underscore | Path(_, _) | Continue(_) | Repeat(_, _) | Paren(_) | Try(_) | Yeet(None) | UnsafeBinderCast(..) | Err(_) | Dummy => { break None; } } } } /// 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) | ast::TyKind::PinnedRef(_, mut_ty) => { ty = &mut_ty.ty; } ast::TyKind::UnsafeBinder(binder) => { ty = &binder.inner_ty; } ast::TyKind::FnPtr(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(_) | ast::GenericBound::Use(..)) | 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, } } } /// 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(_) | ast::GenericArgs::ParenthesizedElided(_) => None, } }