| //! 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::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, | |
| } | |
| } |