| //! 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<TrailingBrace<'_>> { |
| 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, |
| } |
| } |