diff options
Diffstat (limited to 'compiler/rustc_ast/src/util/classify.rs')
| -rw-r--r-- | compiler/rustc_ast/src/util/classify.rs | 193 | 
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, + } +}  | 
